Reputation: 386
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
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
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
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
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