2008-12-24 5 views
264

Gemäß der Dokumentation des == Operator in MSDN,Kann nicht Operator == auf generische Typen in C# angewendet werden?

Bei vordefinierten Wertetypen, die Gleichheitsoperator (==) liefert true, wenn die Werte der Operanden gleich sind, andernfalls false. Bei Referenztypen anders als string, gibt == true zurück, wenn seine beiden Operanden auf dasselbe Objekt verweisen. Beim String-Typ vergleicht == die Werte der Strings. Benutzerdefinierte Werttypen können den Operator == überladen (siehe Operator). So können benutzerdefinierte Referenztypen, obwohl standardmäßig == verhält sich wie oben beschrieben für beide vordefinierte und benutzerdefinierte Referenztypen.

Warum kann dieses Code-Snippet nicht kompiliert werden?

void Compare<T>(T x, T y) { return x == y; } 

erhalte ich die Fehler Operator '==' kann nicht auf Operanden vom Typ 'T' und 'T' angewendet werden. Ich frage mich warum, denn soweit ich das verstehe ist der == Operator für alle Typen vordefiniert?

Bearbeiten: Vielen Dank an alle. Ich habe zunächst nicht bemerkt, dass es in der Aussage nur um Referenztypen ging. Ich dachte auch, dass Bit-für-Bit-Vergleich für alle Werttypen zur Verfügung gestellt wird, die ich jetzt weiß, ist nicht richtig.

Aber wenn ich einen Referenztyp verwende, würde der Operator == den vordefinierten Referenzvergleich verwenden, oder würde er die überladene Version des Operators verwenden, wenn ein Typ definiert ist?

Bearbeiten 2: Durch Versuch und Irrtum erfuhren wir, dass der Operator == den vordefinierten Referenzvergleich verwenden wird, wenn ein unbeschränkter generischer Typ verwendet wird. Tatsächlich wird der Compiler die beste Methode verwenden, die er für das Argument mit eingeschränktem Typ finden kann, wird aber nicht weiter suchen. Zum Beispiel wird der folgende Code immer true drucken, auch wenn Test.test<B>(new B(), new B()) genannt wird:

class A { public static bool operator==(A x, A y) { return true; } } 
class B : A { public static bool operator==(B x, B y) { return false; } } 
class Test { void test<T>(T a, T b) where T : A { Console.WriteLine(a == b); } } 
+0

wieder meine Antwort Siehe die Antwort auf Ihre followup Frage. –

+0

Es kann nützlich sein, zu verstehen, dass es auch ohne Generika einige Typen gibt, bei denen das '==' zwischen zwei Operanden desselben Typs nicht erlaubt ist. Dies gilt für 'struct'-Typen (außer" vordefinierte "Typen), die den' operator == 'nicht überladen. Als einfaches Beispiel versuchen Sie dies: 'var map = typeof (string) .GetInterfaceMap (typeof (ICloneable)); Console.WriteLine (Karte == Karte);/* Kompilierzeit Fehler */' –

+0

Fortsetzung meiner eigenen alten Kommentar. Zum Beispiel (siehe [anderer Thread] (https://stackoverflow.com/questions/6379915/)), mit 'var kvp1 = new KeyValuePair (); var kvp2 = kvp1; ', dann können Sie' kvp1 == kvp2' nicht überprüfen, da 'KeyValuePair <,>' eine Struktur ist, kein vordefinierter C# -Typ ist und den 'operator ==' nicht überlädt. Ein Beispiel ist gegeben durch 'var li = new List (); var e1 = li.GetEnumerator(); var e2 = e1; 'mit dem man' e1 == e2' nicht machen kann (hier haben wir die verschachtelte Struktur 'List <>. Enumerator' ('' '' List'1 + Enumerator [T] "' 'zur Laufzeit)) die nicht '== 'überlädt. –

Antwort

113

"... standardmäßig == verhält sich wie oben für vordefinierte und benutzerdefinierte Referenztypen beschrieben."

Typ T ist nicht unbedingt ein Referenztyp, daher kann der Compiler diese Annahme nicht treffen.

Dies wird jedoch kompilieren, weil es explizit:

bool Compare<T>(T x, T y) where T : class 
    { 
     return x == y; 
    } 

Folgen auf weitere Frage auf: „Aber falls ich einen Referenztyp verwendet wird, würde der der Operator == die Verwendung vordefinierte Referenzvergleich, oder würde es die überladene Version des Operators verwenden, wenn ein Typ definiert? "

Ich hätte gedacht, dass == auf dem Generics die überladene Version verwenden würde, aber der folgende Test zeigt etwas anderes. Interessant ... Ich würde gerne wissen, warum! Wenn jemand weiß, bitte teilen.

namespace TestProject 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
     Test a = new Test(); 
     Test b = new Test(); 

     Console.WriteLine("Inline:"); 
     bool x = a == b; 
     Console.WriteLine("Generic:"); 
     Compare<Test>(a, b); 

    } 


    static bool Compare<T>(T x, T y) where T : class 
    { 
     return x == y; 
    } 
} 

class Test 
{ 
    public static bool operator ==(Test a, Test b) 
    { 
     Console.WriteLine("Overloaded == called"); 
     return a.Equals(b); 
    } 

    public static bool operator !=(Test a, Test b) 
    { 
     Console.WriteLine("Overloaded != called"); 
     return a.Equals(b); 
    } 
    } 
} 

Ausgabe

Inline:

Drücken Sie eine beliebige Taste, um fortzufahren: == überladene

Generika genannt. . .

Follow Up 2

möchte ich darauf hinweisen, dass meine vergleichen Methode

static bool Compare<T>(T x, T y) where T : Test 
    { 
     return x == y; 
    } 

Veränderung bewirkt, dass der überladenen Operator == aufgerufen werden. Ich denke, ohne Angabe des Typs (als wo) kann der Compiler nicht schließen, dass es den überladenen Operator verwenden soll ... obwohl ich denke, dass es genug Informationen hätte, um diese Entscheidung auch ohne Angabe des Typs zu treffen .

+0

Danke. Ich habe nicht bemerkt, dass es in der Aussage nur um Referenztypen ging. –

+4

Re: Follow Up 2: Tatsächlich wird der Compiler die beste gefundene Methode verlinken, in diesem Fall Test.op_Equal. Wenn Sie jedoch eine Klasse haben, die von Test abgeleitet ist und den Operator außer Kraft setzt, wird der Operator von Test immer noch aufgerufen. –

+4

Es ist eine gute Übung, auf die ich hinweisen möchte, dass Sie den tatsächlichen Vergleich immer innerhalb einer überschriebenen 'Equals'-Methode durchführen sollten (nicht im Operator' == '). – jpbochi

12

Die Kompilierung kann nicht wissen, T nicht eine Struktur (Werttyp) sein könnte. Also muss man es sagen, kann es nur von Referenztyp sein ich denke:

bool Compare<T>(T x, T y) where T : class { return x == y; } 

Es weil ist, wenn T ein Werttyp sein könnte, es Fälle geben könnte, wo x == y wäre schlecht gebildet werden - in den Fällen, wenn ein Typ doesn habe keinen Operator == definiert. Das gleiche gilt für diese die geschehen wird, ist offensichtlich:

void CallFoo<T>(T x) { x.foo(); } 

Das ist zu fehlschlägt, weil Sie einen Typ T passieren könnten, die keine Funktion foo haben würde. C# zwingt Sie, sicherzustellen, dass alle möglichen Typen immer eine Funktion foo haben. Das macht die where-Klausel.

+1

Danke für die Klarstellung. Ich wusste nicht, dass Werttypen den Operator == nicht standardmäßig unterstützen. –

+1

Hosam, habe ich mit gmcs (Mono) getestet, und es vergleicht immer Referenzen. (d. h. es wird kein optional definierter Operator == für T verwendet) –

+0

Es gibt eine Einschränkung mit dieser Lösung: der Operator == kann nicht überlastet werden; [Siehe diese StackOverflow-Frage] (http://stackoverflow.com/questions/2919232/using-overloaded-operator-in-a-generic-function). –

1
 

bool Compare(T x, T y) where T : class { return x == y; } 
 

Das obige funktioniert, weil == bei benutzerdefinierten Referenztypen berücksichtigt wird.
Bei Werttypen kann == außer Kraft gesetzt werden. In diesem Fall sollte auch "! =" Definiert werden.

Ich denke, das könnte der Grund sein, es verbietet generischen Vergleich mit "==".

+2

Danke. Ich glaube, dass Referenztypen auch den Operator überschreiben können. Aber der Fehlergrund ist jetzt klar. –

+1

Das Token '==' wird für zwei verschiedene Operatoren verwendet. Wenn für die angegebenen Operandentypen eine kompatible Überladung des Gleichheitsoperators existiert, wird diese Überladung verwendet. Andernfalls, wenn beide Operanden zueinander kompatible Referenztypen sind, wird ein Referenzvergleich verwendet. Beachten Sie, dass der Compiler in der obigen 'Compare'-Methode nicht sagen kann, dass die erste Bedeutung zutrifft, aber er kann sagen, dass die zweite Bedeutung gilt, so dass das' == '-Token das letztere verwendet, auch wenn' T' die Gleichheit überlädt. check operator (zB wenn der Typ 'String' ist) *. – supercat

6

Es scheint, dass ohne die Klasse Einschränkung:

bool Compare<T> (T x, T y) where T: class 
{ 
    return x == y; 
} 

Man sollte erkennen, dass, während classEquals eingeschränkt in den == Betreibern von Object.Equals erbt, während die von einer Struktur ValueType.Equals außer Kraft setzt.

Beachten Sie, dass:

bool Compare<T> (T x, T y) where T: struct 
{ 
    return x == y; 
} 

gibt auch den gleichen Compiler-Fehler aus.

Bis jetzt verstehe ich nicht, warum ein Gleichheitsoperatorvergleich vom Werttyp vom Compiler abgelehnt wird.Ich weiß allerdings, für eine Tatsache, dass dies funktioniert:

bool Compare<T> (T x, T y) 
{ 
    return x.Equals(y); 
} 
+0

Sie wissen, ich bin insgesamt C# noob. aber ich denke, es scheitert, weil der Compiler nicht weiß, was zu tun ist. Da T noch nicht bekannt ist, hängt das, was getan wird, vom Typ T ab, wenn Werttypen zulässig wären. Bei Referenzen werden die Referenzen einfach verglichen, unabhängig von T. Wenn Sie .Equals verwenden, dann wird .Equal gerade aufgerufen. –

+0

Wenn Sie jedoch == für einen Werttyp eingeben, muss der Werttyp diesen Operator nicht implementieren. –

+0

Das würde Sinn machen, litb :) Es ist möglich, dass benutzerdefinierte Strukturen nicht überladen ==, daher der Compiler fehlschlagen. –

249

Wie andere gesagt haben, es wird nur funktionieren, wenn T gezwungen ist, ein Referenztyp zu sein. Ohne Einschränkungen können Sie mit null vergleichen, aber nur mit null - und dieser Vergleich ist immer falsch für Nicht-Nullable-Werttypen.

Statt Equals zu nennen, dann ist es besser, ein IComparer<T> zu verwenden - und wenn Sie keine weiteren Informationen haben, ist EqualityComparer<T>.Default eine gute Wahl:

public bool Compare<T>(T x, T y) 
{ 
    return EqualityComparer<T>.Default.Equals(x, y); 
} 

von etwas Neben anderen, dies vermeidet Box/Gießen.

+0

Danke. Ich habe versucht, eine einfache Wrapper-Klasse zu schreiben, also wollte ich die Operation nur an das tatsächlich umschlossene Member delegieren. Aber das Wissen um EqualityComparer .Default sicherlich einen Mehrwert für mich. :) –

+0

Minor beiseite, Jon; Vielleicht möchten Sie den Kommentar re pobox vs yoda auf meinem Beitrag zu beachten. –

+4

Netter Tipp für die Verwendung von EqualityComparer chakrit

34

Im Allgemeinen sollte EqualityComparer<T>.Default.Equals die Arbeit mit allem, das IEquatable<T> implementiert, oder das eine sinnvolle Implementierung Equals hat.

Wenn jedoch == und Equals aus irgendeinem Grund anders implementiert werden, dann sollte meine Arbeit an generic operators nützlich sein; Es unterstützt die Betreiber Versionen von (unter anderem):

  • Equal (T value1, T Wert2)
  • Ungleich (T value1, T Wert2)
  • GreaterThan (T value1, T Wert2)
  • LessThan (T Wert1, Wert2 T)
  • GreaterThanOrEqual (T Wert1, T Wert2)
  • LessThanOrEqual (T Wert1, T Wert2)
+0

Sehr interessante Bibliothek! :) (Randnotiz: Darf ich vorschlagen, den Link zu www.yoda.arachsys.com, weil die pobox wurde von der Firewall in meinem Arbeitsplatz blockiert? Es ist möglich, dass andere können das gleiche Problem.) –

+0

Interessant (die pobox/yoda Sache). Ich werde mich daran erinnern ... –

+0

Die Idee ist, dass http://pobox.com/~skeet * immer * auf meine Website zeigen wird - auch wenn es sich anderswo bewegt. Ich tendiere dazu, Links über pobox.com zu hinterlassen, um der Nachwelt gerecht zu werden - aber Sie können stattdessen yoda.arachsys.com ersetzen. –

4

Es ist ein MSDN Connect-Eintrag für diesen here

Alex Antwort Turner beginnt mit:

Leider ist dieses Verhalten von Design und es gibt keine einfache Lösung Verwendung von == zu ermöglichen mit Typ Parameter, die Wert Typen enthalten können.

21

So viele Antworten, und nicht eine einzige erklärt das WARUM? (was Giovanni explizit gefragt hat) ...

.NET-Generics funktionieren nicht wie C++ - Vorlagen. In C++ - Vorlagen tritt die Überladungsauflösung auf, nachdem die tatsächlichen Vorlagenparameter bekannt sind.

In .NET-Generics (einschließlich C#) tritt die Überladungsauflösung auf, ohne die tatsächlichen generischen Parameter zu kennen. Die einzige Information, die der Compiler verwenden kann, um die aufzurufende Funktion auszuwählen, kommt von Typbeschränkungen für die generischen Parameter.

+2

aber warum kann Compiler sie nicht als generisches Objekt behandeln? nach allem funktioniert '==' für alle Typen, seien es Referenztypen oder Werttypen. Das sollte die Frage sein, auf die ich glaube, dass du nicht geantwortet hast. – nawfal

+2

@Nawfal: Eigentlich nein, '==' funktioniert nicht für alle Werttypen. Noch wichtiger ist, dass es nicht für alle Typen dieselbe Bedeutung hat, daher weiß der Compiler nicht, was er damit tun soll. –

+1

Ben, oh ja, ich habe die benutzerdefinierten Strukturen vermisst, die wir ohne '==' erstellen können. Kannst du diesen Teil auch in deine Antwort miteinbeziehen, wie ich denke, das ist der Hauptpunkt hier – nawfal

4

Wenn Sie sicherstellen möchten, dass die Operatoren Ihres benutzerdefinierten Typs aufgerufen werden, können Sie dies über die Reflektion tun. Holen Sie sich einfach den Typ mit Ihrem generischen Parameter und rufen Sie die MethodInfo für den gewünschten Operator ab (z. B. op_Equality, op_Inequality, op_LessThan ...).

var methodInfo = typeof (T).GetMethod("op_Equality", 
          BindingFlags.Static | BindingFlags.Public);  

auszuführen Dann der Bediener die Method des Invoke-Methode und in die Objekte als Parameter übergeben.

var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2}); 

Dies ruft Ihren überladenen Operator auf und nicht den, der durch die Einschränkungen definiert ist, die für den generischen Parameter gelten. Dies ist möglicherweise nicht praktisch, könnte sich aber als nützlich erweisen, wenn Sie Ihre Operatoren testen möchten, wenn Sie eine generische Basisklasse verwenden, die einige Tests enthält.

1

Ich schrieb die folgende Funktion mit Blick auf die neueste msdn. Es kann leicht zwei Objekte x und y vergleichen:

static bool IsLessThan(T x, T y) 
{ 
    return ((IComparable)(x)).CompareTo(y) <= 0; 
} 
+3

Sie können Ihre Booleans loswerden und 'return ((IComparable) (x)) schreiben. CompareTo (y) <= 0;' – aloisdg

2

Nun, in meinem Fall ich Unit-Test wollte den Gleichheitsoperator. Ich brauchte den Code unter den Gleichheitsoperatoren aufrufen, ohne explizit den generischen Typ zu setzen. Hinweise für EqualityComparer waren nicht hilfreich wie EqualityComparer genannt Equals Methode, aber nicht der Gleichheitsoperator.

Hier ist, wie ich dies mit generischen Typen arbeiten durch den Aufbau einer LINQ. Er fordert den richtigen Code für == und != Operatoren:

/// <summary> 
/// Gets the result of "a == b" 
/// </summary> 
public bool GetEqualityOperatorResult<T>(T a, T b) 
{ 
    // declare the parameters 
    var paramA = Expression.Parameter(typeof(T), nameof(a)); 
    var paramB = Expression.Parameter(typeof(T), nameof(b)); 
    // get equality expression for the parameters 
    var body = Expression.Equal(paramA, paramB); 
    // compile it 
    var invokeEqualityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile(); 
    // call it 
    return invokeEqualityOperator(a, b); 
} 

/// <summary> 
/// Gets the result of "a =! b" 
/// </summary> 
public bool GetInequalityOperatorResult<T>(T a, T b) 
{ 
    // declare the parameters 
    var paramA = Expression.Parameter(typeof(T), nameof(a)); 
    var paramB = Expression.Parameter(typeof(T), nameof(b)); 
    // get equality expression for the parameters 
    var body = Expression.NotEqual(paramA, paramB); 
    // compile it 
    var invokeInequalityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile(); 
    // call it 
    return invokeInequalityOperator(a, b); 
}