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);
}
The Equals method uses ordinal comparison to determine whether the strings are equal. . NET Core 3.0 and later versions: The current instance and sb are equal if the strings assigned to both StringBuilder objects are the same. To determine equality, the Equals method uses ordinal comparison.
We can use the equals() method for comparing two strings in Java since the String class overrides the equals() method of the Object class, while StringBuilder doesn't override the equals() method of the Object class and hence equals() method cannot be used to compare two StringBuilder objects.
Syntax: public bool Equals (System. Text. StringBuilder sb); Here, sb is an object to compare with this instance, or null. Return Value: It will return true if this instance and sb have an equal string, Capacity, and MaxCapacity values; otherwise, false.
StringBuilder and String are completely different objects.
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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With