Проверка 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, потому что литерал генерируется только один раз, а затем будет указан каждый раз, когда он будет использоваться.