2017-07-07 2 views
15

Ich versuche dieses Beispiel Code und OpTest, wenn System.Console.WriteLine(s == t);false zurückgibt. Kann jemand das erklären?StringBuilder und String-Gleichheitsprüfung

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

Es hilft, die IL z.B. Verwenden Sie Ildasm, um diese Art von Verhalten selbst zu erforschen. – Jeroen

+0

Wenn Sie für die Gleichheit in generischem Code vergleichen möchten, verwenden Sie 'EqualityComparer .Default.Equals (s, t)' und ermöglichen Sie dem Benutzer optional, seinen eigenen 'IEqualityComparer ' zu übergeben. – CodesInChaos

+0

Mögliches Duplikat von [C# -Differenz zwischen == und Equals()] (https://stackoverflow.com/questions/814878/c-sharp-difference-between-and-equals) – Dukeling

Antwort

16

Ihre generische Methode wird im Grunde eine Referenz Gleichheitsprüfung durchführen - und die Werte von s1 und s2 beziehen sich auf unterschiedliche, aber gleich Saiten. Sie können wie folgt dies leichter zeigen:

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 

Beachten Sie, dass Ihre Einschränkung in OpTest ändert nicht die == Operator verwendet wird. Dies wird zur Kompilierungszeit bestimmt, basierend auf den Einschränkungen auf T. Beachten Sie, dass Operatoren niemals überschrieben werden, nur überladen. Das bedeutet, dass die Implementierung zur Kompilierungszeit unabhängig vom Typ zur Ausführungszeit ausgewählt wird.

Wenn Sie T von einem Typ ableiten, der den Operator == überlastet, wird der Compiler diese Überladung verwenden. Zum Beispiel:

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

Hier ist die OpTest Methode die überladenen Operatoren nicht verwendet - aber immer nur die, die von SillyClass, nicht SillySubclass.

2

s == t in OpTest<T> Methode prüft auf Referenzgleichheit, nicht Wertgleichheit. In diesem Fall gibt es false aufgrund der Differenz der Referenzquelle der beiden StringBuilder Klasse zurück.

true Wert zu erhalten, müssen Sie Equals Methode verwenden:

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

Demo: .NET Fiddle Example

+0

Randnotiz: falls 's' ist null: 'string.Equals (s, t)'. – JohnLBevan

2

Dies geschieht, weil Sie eine generische Methode verwenden und Sie gezielt den generischen Parameter Art von class beschränken .

Standardmäßig ist für generische Typen der Gleichheitsoperator == nicht definiert.

Die Beschränkung der möglichen Typen von <T> auf Klasse macht die Verwendung von s == t möglich. Jetzt wird jedoch die Standardimplementierung verwendet, die durch die class-Einschränkung angegeben ist und die die Referenzgleichheit verwendet.

Da einer Ihrer Strings von StringBuilder stammt, wird eine neue Referenz erstellt, obwohl der Inhalt der Zeichenfolge gleich ist.

Wenn Sie in beiden Fällen dasselbe Zeichenfolgenliteral verwenden, wird jedoch true zurückgegeben, da das Literal nur einmal generiert wird und dann bei jeder Verwendung referenziert wird.

4

Es gibt viele Antworten bereits, aber ich habe etwas extra hinzuzufügen. Wenn Sie bei dieser Art von Problem stecken bleiben, kann es hilfreich sein, ildasm.exe zu verwenden, um die generierte IL zu betrachten.Zum Beispiel:

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

gibt für 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 

Sie sehen also, es ceq nennt, die für Referenz Gleichheit überprüft.

Der andere hat dieses 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 

Das ist nicht ceq nicht verwendet, aber die Zeichenfolge Gleichheit Betrieb in mscorlib und wird zu einem Ergebnis wie erwartet.

Wie ich schon sagte, nur um eine andere Art der Erforschung dieses Problems hinzuzufügen. Für detailliertere Informationen empfehle ich das Lesen von @JonSkeet's answer.

+0

"Sie sehen also, dass beide Saiten gepackt werden" - nicht wirklich. Die 'box'-Anweisung ist hier etwas sinnlos angesichts der Einschränkung ... sie wird nur irgendetwas tun, wenn das Typargument ein Werttyp ist, der nicht hier ist. –

+0

@JonSkeet Vielen Dank für die Korrektur, lernen, wie ich hier gehe: D. Ich habe diesen Kommentar einfach entfernt und hoffe, dass meine Antwort richtig ist. (Ich war mir nicht sicher, wie ich den zweiten Teil Ihrer Bemerkung in meine Antwort einbauen sollte, aber wenn Sie sehen, wie und wie ich denke, dass es sich um die Antwort handelt, können Sie es bearbeiten.) – Jeroen