StringBuilder and string equality check

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);
}

Upvotes: 15

Views: 1321

Answers (4)

Jeroen
Jeroen

Reputation: 63800

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.

Upvotes: 4

Adwaenyth
Adwaenyth

Reputation: 2110

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.

Upvotes: 2

Jon Skeet
Jon Skeet

Reputation: 1502816

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.

Upvotes: 16

Tetsuya Yamamoto
Tetsuya Yamamoto

Reputation: 24957

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

Upvotes: 2

Related Questions