Проверка StringBuilder и проверка равенства строк
Я пытаюсь использовать этот образец кода и OpTest
, когда System.Console.WriteLine(s == t);
возвращает false
. Может кто-нибудь объяснить это?
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);
}
Ответы
Ответ 1
Ваш общий метод будет в основном выполнять контрольную проверку равенства, а значения s1
и s2
относятся к разным, но равным строкам. Вы можете показать это проще:
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
Обратите внимание, что ваше ограничение в OpTest
не меняет, какой оператор ==
используется. Это определяется во время компиляции, основываясь на ограничениях на T
. Обратите внимание, что операторы никогда не переопределяются, только перегружены. Это означает, что реализация выбирается во время компиляции независимо от типа во время выполнения.
Если вы ограничены T
для получения какого-либо типа, который перегружает оператор ==
, тогда компилятор будет использовать эту перегрузку. Например:
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);
}
}
Здесь метод OpTest
использует перегруженные операторы, но только те, что из SillyClass
, а не SillySubclass
.
Ответ 2
Уже есть много ответов, но у меня есть что-то дополнительное для добавления. Если вы застряли в этом вопросе, это может помочь использовать ildasm.exe
для поиска сгенерированного ИЛ. Например:
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;
//}
}
Дает для 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
Итак, вы видите, что он вызывает ceq
, который проверяет ссылочное равенство.
Другой имеет этот 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
Это не использует ceq
, а операцию равенства строк в mscorlib
и даст результат, как ожидалось.
Как я уже сказал, просто добавим еще один способ исследования этой проблемы. Для более подробных сведений о более высоком уровне я бы рекомендовал прочитать @JonSkeet answer.
Ответ 3
s == t
в OpTest<T>
метод проверяет ссылочное равенство, а не равенство равенства. В этом случае он возвращает false
из-за разности исходного источника как класса StringBuilder
.
Чтобы получить значение true
, вам нужно использовать метод Equals
:
public static void OpTest<T>(T s, T t) where T : class
{
System.Console.WriteLine(s.Equals(t));
}
Демо: . Пример .NET Fiddle
Ответ 4
Это происходит потому, что вы используете общий метод, и вы конкретно ограничиваете общий параметр типом class
.
По умолчанию общие типы не имеют определенного оператора равенства ==
.
Ограничение возможных типов <T>
на класс делает возможным использование s == t
. Однако теперь он будет использовать реализацию по умолчанию, указанную в ограничении class
, и которая использует ссылочное равенство.
Поскольку одна из ваших строк поступает из StringBuilder
, она создаст новую ссылку, хотя содержимое строки будет одинаковым.
Если вы используете один и тот же строковый литерал в обоих случаях, он, однако, вернет true
, потому что литерал генерируется только один раз, а затем будет указан каждый раз, когда он будет использоваться.