15

I am trying this sample of code and OpTest when System.Console.WriteLine(s == t); it returns false. Can somebody explain this?

public static void OpTest<T>(T s, T t) where T : class 
{
    System.Console.WriteLine(s == t);
}
static void Main() 
{   
    string s1 = "строка";
    System.Text.StringBuilder sb = new System.Text.StringBuilder(s1);
    System.Console.Write(sb);
    string s2 = sb.ToString();
    OpTest<string>(s1, s2);
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • It helps to look at the IL e.g. using ildasm to research this kind of behavior yourself. – Jeroen Jul 07 '17 at 07:32
  • If you want to compare for equality in generic code use `EqualityComparer.Default.Equals(s, t)` and optionally allow the user to pass their own `IEqualityComparer`. – CodesInChaos Jul 07 '17 at 10:30
  • Possible duplicate of [C# difference between == and Equals()](https://stackoverflow.com/questions/814878/c-sharp-difference-between-and-equals) – Bernhard Barker Jul 07 '17 at 11:05

4 Answers4

16

Your generic method will basically be performing a reference equality check - and the values of s1 and s2 refer to different but equal strings. You can show this more easily like this:

string x = "test";
string y = new string(x.ToCharArray());
Console.WriteLine(x == y); // Use string overload, checks for equality, result = true
Console.WriteLine(x.Equals(y)); // Use overridden Equals method, result = true
Console.WriteLine(ReferenceEquals(x, y)); // False because they're different objects
Console.WriteLine((object) x == (object) y); // Reference comparison again - result = false

Note that your constraint in OpTest doesn't change which == operator is used. That's determined at compile-time, based on the constraints on T. Note that operators are never overridden, only overloaded. That means the implementation is chosen at compile-time, regardless of the type at execution time.

If you constrained T to derive from some type which overloads the == operator, then the compiler will use that overload. For example:

using System;

class SillyClass
{
    public static string operator ==(SillyClass x, SillyClass y) => "equal";
    public static string operator !=(SillyClass x, SillyClass y) => "not equal";
}

class SillySubclass : SillyClass
{
    public static string operator ==(SillySubclass x, SillySubclass y) => "sillier";
    public static string operator !=(SillySubclass x, SillySubclass y) => "very silly";
}

class Test
{
    static void Main()
    {
        var x = new SillySubclass();
        var y = new SillySubclass();
        OpTest(x, y);
    }

    static void OpTest<T>(T x, T y) where T : SillyClass
    {
        Console.WriteLine(x == y);
        Console.WriteLine(x != y);
    }
}

Here the OpTest method does use the overloaded operators - but only ever the ones from SillyClass, not SillySubclass.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
4

There are many answers already, but I have something extra to add. If you're stuck on this kind of issue it can help to use ildasm.exe to look at the generated IL. For example:

public class Foo
{
    public static void OpTest_1<T>(T s, T t) where T : class
    {
        var val = s == t;
    }

    public static void OpTest_2(string s, string t)
    {
        var val = s == t;
    }

    // Does not compile.
    //public static void OpTest_3<T>(T s, T t) where T : struct
    //{
    //    var val = s == t;
    //}
}

Gives for OpTest_1:

.method public hidebysig static void  OpTest_1<class T>(!!T s, !!T t) cil managed
{
  // Code size       17 (0x11)
  .maxstack  2
  .locals init ([0] bool val)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  box        !!T
  IL_0007:  ldarg.1
  IL_0008:  box        !!T
  IL_000d:  ceq
  IL_000f:  stloc.0
  IL_0010:  ret
} // end of method Foo::OpTest_1

So you see it calls ceq which checks for reference equality.

The other one has this IL:

.method public hidebysig static void  OpTest_2(string s, string t) cil managed
{
  // Code size       10 (0xa)
  .maxstack  2
  .locals init ([0] bool val)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldarg.1
  IL_0003:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_0008:  stloc.0
  IL_0009:  ret
} // end of method Foo::OpTest_2

That doesn't use ceq but the string equality operation in mscorlib and will give a result as expected.

Like I said, just to add another way of researching this issue. For more high level details I'd recommend reading @JonSkeet's answer.

Jeroen
  • 60,696
  • 40
  • 206
  • 339
  • "So you see it does box both strings" - not really. The `box` instruction here is somewhat pointless given the constraint... it will only do anything if the type argument is a value type, which it's not here. – Jon Skeet Jul 07 '17 at 10:06
  • @JonSkeet Thank you for the correction, learning as I go here :D. I've plainly removed that comment, hopefully making my answer correct. (Wasn't sure how to incorporate the second part of your remark in my answer, but if you see how and think it adds to the answer feel free to edit.) – Jeroen Jul 07 '17 at 10:34
2

s == t in OpTest<T> method checks for reference equality, not value equality. In this case, it returns false due to difference of reference source of both StringBuilder class.

To get true value, you need to use Equals method:

public static void OpTest<T>(T s, T t) where T : class 
{
    System.Console.WriteLine(s.Equals(t));
}

Demo: .NET Fiddle Example

Tetsuya Yamamoto
  • 24,297
  • 8
  • 39
  • 61
2

This happens because you are using a generic method and you specifically restrict the generic parameter to type of class.

By default, generic types do not have the equality operator == defined.

Restricting the possible types of <T>to class makes the use of s == t possible. However, now it will use the default implementation specified by the class restriction and that is using reference equality.

Since one of your strings comes from the StringBuilder it will create a new reference although the string's contents are the same.

If you use the same string literal in both cases, it will however return true because the literal is only generated once and then that will be referenced each time it is used.

Adwaenyth
  • 2,020
  • 12
  • 24