2017-07-07 16 views
15

Próbuję tej próbki kodu i OpTest, gdy System.Console.WriteLine(s == t); zwraca false. Czy ktoś może to wyjaśnić?StringBuilder i sprawdzanie równości łańcuchów

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

Pomaga spojrzeć na IL, np. używając ildazu do samodzielnego badania tego rodzaju zachowań. – Jeroen

+0

Jeśli chcesz porównać dla równości w kodzie generycznym, użyj 'EqualityComparer .Default.Equals (s, t)' i opcjonalnie pozwól użytkownikowi przekazać własny 'IEqualityComparer '. – CodesInChaos

+0

Prawdopodobny duplikat [C# różnicy między == i Equals()] (https://stackoverflow.com/questions/814878/c-sharp-difference-between-and-equals) – Dukeling

Odpowiedz

16

Twoja metoda rodzajowa będzie w istocie być wykonanie odniesienia sprawdzanie równości - a wartości s1 i s2 odnoszą się do różnych, ale równych strun. Można pokazać to łatwiej tak:

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 

pamiętać, że ograniczenie w OpTest nie zmienia który == operator jest używany. To jest określane podczas kompilacji, w oparciu o ograniczenia na T. Zauważ, że operatorzy nigdy nie są przesłaniany, tylko przeładowany. Oznacza to, że implementacja jest wybierana podczas kompilacji, niezależnie od typu w czasie wykonywania.

Jeśli ograniczyłeś T, aby wyprowadzić z jakiegoś typu, który przeciąży operator ==, kompilator użyje tego przeciążenia. Na przykład:

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

Oto metoda OpTestma użyć przeciążone operatory - ale zawsze tylko te z SillyClass, nie SillySubclass.

2

w OpTest<T> sprawdzanie metod dla równości referencyjnej, nierówność wartości. W takim przypadku zwraca false ze względu na różnicę źródła odwołania obu klas: StringBuilder.

Aby uzyskać true wartość, trzeba użyć Equals metody:

public static void OpTest<T>(T s, T t) where T : class 
{ 
    System.Console.WriteLine(s.Equals(t)); 
} 

Demo: .NET Fiddle Example

+0

Nota boczna: w przypadku 's' jest null: 'string.Equals (s, t)'. – JohnLBevan

2

Dzieje się tak dlatego, że przy użyciu metody rodzajowe i specjalnie ograniczyć parametru rodzajowego do rodzaju class . Domyślnie typy ogólne nie mają zdefiniowanego operatora równości ==.

Ograniczenie możliwych typów <T> do klasy umożliwia korzystanie z s == t. Jednak teraz będzie używał domyślnej implementacji określonej przez ograniczenie class i używa równości odniesienia.

Ponieważ jeden z twoich napisów pochodzi z StringBuilder, utworzy nowe odniesienie, chociaż zawartość napisu będzie taka sama.

Jeśli użyjesz tego samego ciągu literowego w obu przypadkach, zwróci on jednak true, ponieważ literał jest generowany tylko raz, a następnie będzie odnosił się do niego za każdym razem, gdy zostanie użyty.

4

Istnieje już wiele odpowiedzi, ale muszę dodać coś ekstra. Jeśli utknąłeś na tego rodzaju problemie, może pomóc użyć ildasm.exe do obejrzenia wygenerowanej IL.Na przykład:

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

Daje do 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 

Więc widać wywołuje ceq który sprawdza odniesienia równości.

Drugi ma ten 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 

która nie korzysta ceq ale operacja równości smyczkowy mscorlib i dadzą rezultatu, jak oczekiwano.

Tak jak powiedziałem, aby dodać inny sposób badania tego problemu. Aby uzyskać więcej szczegółów na wysokim poziomie, polecam lekturę @JonSkeet's answer.

+0

"Tak więc widzisz, że nie ma w nim dwóch ciągów" - nie bardzo. Instrukcja 'box' jest tu nieco bezsensowna, biorąc pod uwagę ograniczenie ... zrobi wszystko tylko, jeśli argument type jest typem wartości, którego nie ma tutaj. –

+0

@JonSkeet Dziękuję za poprawkę, uczę się jak tutaj: D. Jasno usunąłem ten komentarz, miejmy nadzieję, że poprawiam swoją odpowiedź. (Nie byłem pewny, jak uwzględnić drugą część uwagi w mojej odpowiedzi, ale jeśli widzisz, jak i myślę, że dodaje ona do odpowiedzi, możesz ją edytować). – Jeroen