Da sonst niemand diese Antwort ausdrücklich gegeben hat, werde ich Folgendes hinzufügen:
Das Implementieren einer Schnittstelle in einer Struktur hat keinerlei negative Konsequenzen.
Jede Variable des Schnittstellentyps, die zum Halten einer Struktur verwendet wird, führt dazu, dass ein Boxwert dieser Struktur verwendet wird. Wenn die Struktur unveränderlich ist (eine gute Sache), ist dies im schlimmsten Fall ein Leistungsproblem, es sei denn, Sie sind:
- Verwenden des resultierenden Objekts zum Sperren (auf jeden Fall eine immens schlechte Idee)
- Verwenden der Referenzgleichheitssemantik und Erwarten, dass sie für zwei Boxwerte aus derselben Struktur funktioniert.
Beides ist unwahrscheinlich, stattdessen führen Sie wahrscheinlich eine der folgenden Aktionen aus:
Generika
Möglicherweise gibt es viele vernünftige Gründe für Strukturen, die Schnittstellen implementieren, damit sie in einem generischen Kontext mit Einschränkungen verwendet werden können . Bei Verwendung auf diese Weise kann die Variable wie folgt aussehen:
class Foo<T> : IEquatable<Foo<T>> where T : IEquatable<T>
{
private readonly T a;
public bool Equals(Foo<T> other)
{
return this.a.Equals(other.a);
}
}
- Aktivieren Sie die Verwendung der Struktur als Typparameter
- solange keine andere Einschränkung wie
new()
oder class
verwendet wird.
- Ermöglichen Sie das Vermeiden des Boxens bei Strukturen, die auf diese Weise verwendet werden.
Dann ist this.a KEINE Schnittstellenreferenz, daher verursacht es keine Box mit dem, was darin platziert ist. Wenn der c # -Compiler die generischen Klassen kompiliert und Aufrufe der Instanzmethoden einfügen muss, die für Instanzen des Typparameters T definiert sind, kann er den eingeschränkten Opcode verwenden:
Wenn thisType ein Werttyp ist und thisType eine Methode implementiert, wird ptr unverändert als 'this'-Zeiger auf eine Aufrufmethodenanweisung für die Implementierung der Methode durch thisType übergeben.
Dies vermeidet das Boxen und da der Werttyp implementiert wird, muss die Schnittstelle die Methode implementieren, so dass kein Boxen auftritt. Im obigen Beispiel erfolgt der Equals()
Aufruf ohne Kästchen. A 1 .
Reibungsarme APIs
Die meisten Strukturen sollten eine primitive Semantik haben, bei der bitweise identische Werte als gleich 2 betrachtet werden . Die Laufzeit liefert ein solches Verhalten implizit Equals()
, dies kann jedoch langsam sein. Auch diese implizite Gleichheit wird nicht als Implementierung von IEquatable<T>
offengelegt und verhindert somit, dass Strukturen leicht als Schlüssel für Wörterbücher verwendet werden können, es sei denn, sie implementieren sie explizit selbst. Es ist daher üblich, dass viele öffentliche Strukturtypen deklarieren, dass sie implementiert werden IEquatable<T>
(wo T
sind sie selbst), um dies einfacher und leistungsfähiger zu machen und mit dem Verhalten vieler vorhandener Werttypen innerhalb der CLR-BCL übereinzustimmen.
Alle Grundelemente in der BCL implementieren mindestens:
IComparable
IConvertible
IComparable<T>
IEquatable<T>
(Und so IEquatable
)
Viele implementieren auch IFormattable
, viele der vom System definierten Werttypen wie DateTime, TimeSpan und Guid implementieren auch viele oder alle. Wenn Sie einen ähnlich "weit verbreiteten" Typ wie eine komplexe Zahlenstruktur oder einige Textwerte mit fester Breite implementieren, wird Ihre Struktur durch die korrekte Implementierung vieler dieser allgemeinen Schnittstellen nützlicher und benutzerfreundlicher.
Ausschlüsse
Wenn die Schnittstelle eine starke Veränderbarkeit (wie z. B. ICollection
) impliziert, ist die Implementierung offensichtlich eine schlechte Idee, da dies bedeuten würde, dass Sie entweder die Struktur veränderbar gemacht haben (was zu den bereits beschriebenen Fehlern führt, bei denen die Änderungen am Boxed-Wert und nicht am Original auftreten ) oder Sie verwirren Benutzer, indem Sie die Auswirkungen der Methoden wie Add()
oder Ausnahmen ignorieren .
Viele Schnittstellen implizieren KEINE Veränderlichkeit (wie z. B. IFormattable
) und dienen als idiomatische Methode, um bestimmte Funktionen auf konsistente Weise verfügbar zu machen. Oft kümmert sich der Benutzer der Struktur nicht um den Boxaufwand für ein solches Verhalten.
Zusammenfassung
Wenn dies bei unveränderlichen Werttypen sinnvoll durchgeführt wird, ist die Implementierung nützlicher Schnittstellen eine gute Idee
Anmerkungen:
1: Beachten Sie, dass der Compiler dies möglicherweise verwendet, wenn er virtuelle Methoden für Variablen aufruft, von denen bekannt ist , dass sie von einem bestimmten Strukturtyp sind, in denen jedoch eine virtuelle Methode aufgerufen werden muss. Beispielsweise:
List<int> l = new List<int>();
foreach(var x in l)
;//no-op
Der von der Liste zurückgegebene Enumerator ist eine Struktur, eine Optimierung, um eine Zuordnung bei der Aufzählung der Liste zu vermeiden (mit einigen interessanten Konsequenzen ). Doch die Semantik von foreach , die angeben , ob die enumerator Geräte IDisposable
dann Dispose()
wird einmal aufgerufen werden , die Iteration abgeschlossen ist. Wenn dies offensichtlich durch einen Boxed Call geschieht, würde jeder Vorteil des Enumerators als Struktur beseitigt (tatsächlich wäre es schlimmer). Schlimmer noch, wenn dispose call den Status des Enumerators auf irgendeine Weise ändert, würde dies auf der Boxed-Instanz passieren und in komplexen Fällen können viele subtile Fehler auftreten. Daher ist die in dieser Art von Situation ausgestrahlte IL:
IL_0001: newobj System.Collections.Generic.List..ctor
IL_0006: stloc.0
IL_0007: nein
IL_0008: ldloc.0
IL_0009: callvirt System.Collections.Generic.List.GetEnumerator
IL_000E: stloc.2
IL_000F: br.s IL_0019
IL_0011: ldloca.s 02
IL_0013: Rufen Sie System.Collections.Generic.List.get_Current auf
IL_0018: stloc.1
IL_0019: ldloca.s 02
IL_001B: Rufen Sie System.Collections.Generic.List.MoveNext auf
IL_0020: stloc.3
IL_0021: ldloc.3
IL_0022: brtrue.s IL_0011
IL_0024: Leave.s IL_0035
IL_0026: ldloca.s 02
IL_0028: eingeschränkt. System.Collections.Generic.List.Enumerator
IL_002E: callvirt System.IDisposable.Dispose
IL_0033: Nein
IL_0034: Endgültig
Somit verursacht die Implementierung von IDisposable keine Leistungsprobleme und der (bedauerliche) veränderbare Aspekt des Enumerators bleibt erhalten, falls die Dispose-Methode tatsächlich etwas tut!
2: double und float sind Ausnahmen von dieser Regel, bei denen NaN-Werte nicht als gleich angesehen werden.