In dieser Frage sind mehrere Dinge im Gange ...
Es ist möglich, dass eine Struktur eine Schnittstelle implementiert, es gibt jedoch Bedenken hinsichtlich Casting, Veränderbarkeit und Leistung. Weitere Informationen finden Sie in diesem Beitrag: https://docs.microsoft.com/en-us/archive/blogs/abhinaba/c-structs-and-interface
Im Allgemeinen sollten Strukturen für Objekte mit Werttypsemantik verwendet werden. Durch die Implementierung einer Schnittstelle in einer Struktur können Boxprobleme auftreten, wenn die Struktur zwischen der Struktur und der Schnittstelle hin und her geworfen wird. Infolge des Boxens verhalten sich Operationen, die den internen Status der Struktur ändern, möglicherweise nicht richtig.
IComparable<T>
undIEquatable<T>
. Das Speichern einer StrukturFoo
in einer Variablen vom TypIComparable<Foo>
würde Boxing erfordern. Wenn jedoch ein generischer TypT
aufIComparable<T>
einen beschränkt ist, kann er mit einem anderen verglichen werden,T
ohne dass einer der beiden Boxen erforderlich ist und ohne dass Sie etwasT
anderes wissen müssen, als dass er die Einschränkung implementiert. Ein solches vorteilhaftes Verhalten wird nur durch die Fähigkeit von Strukturen ermöglicht, Schnittstellen zu implementieren. Das wurde gesagt ...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:
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:
new()
oderclass
verwendet wird.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:
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 vonIEquatable<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 werdenIEquatable<T>
(woT
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 soIEquatable
)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 wieAdd()
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:
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
dannDispose()
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: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.
quelle
struct
in a sollte eine Compiler-Warnung angezeigt werdeninterface
.In einigen Fällen kann es für eine Struktur gut sein, eine Schnittstelle zu implementieren (wenn dies niemals nützlich gewesen wäre, wäre es zweifelhaft, ob die Ersteller von .net dies vorgesehen hätten). Wenn eine Struktur eine schreibgeschützte Schnittstelle wie implementiert, erfordert das
IEquatable<T>
Speichern der Struktur an einem Speicherort (Variable, Parameter, Array-Element usw.) vom TypIEquatable<T>
, dass sie eingerahmt ist (jeder Strukturtyp definiert tatsächlich zwei Arten von Dingen: einen Speicher Standorttyp, der sich wie ein Werttyp verhält, und ein Heap-Objekttyp, der sich wie ein Klassentyp verhält, der erste ist implizit in den zweiten konvertierbar - "Boxing" - und der zweite kann durch explizite Umwandlung in den ersten konvertiert werden. "Unboxing"). Es ist jedoch möglich, die Implementierung einer Schnittstelle durch eine Struktur ohne Boxen auszunutzen, indem sogenannte Constrained Generics verwendet werden.Wenn man
CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T>
beispielsweise eine Methode hätte , könnte eine solche Methode aufrufen,thing1.Compare(thing2)
ohne boxen zu müssenthing1
oderthing2
. Wennthing1
dies z. B. ein istInt32
, weiß die Laufzeit, wann sie den Code für generiertCompareTwoThings<Int32>(Int32 thing1, Int32 thing2)
. Da es den genauen Typ sowohl der Sache kennt, die die Methode hostet, als auch der Sache, die als Parameter übergeben wird, muss es keine von beiden boxen.Das größte Problem bei Strukturen, die Schnittstellen implementieren, besteht darin, dass sich eine Struktur, die an einem Ort des Schnittstellentyps gespeichert wird
Object
, oderValueType
(im Gegensatz zu einem Ort ihres eigenen Typs) als Klassenobjekt verhält. Für schreibgeschützte Schnittstellen ist dies im Allgemeinen kein Problem, aber für eine mutierende Schnittstelle wieIEnumerator<T>
diese kann es zu einer seltsamen Semantik kommen.Betrachten Sie beispielsweise den folgenden Code:
Die markierte Anweisung Nr. 1 wird
enumerator1
das erste Element lesen. Der Status dieses Enumerators wird in kopiertenumerator2
. Die markierte Anweisung Nr. 2 erweitert diese Kopie, um das zweite Element zu lesen, hat jedoch keine Auswirkungenenumerator1
. Der Status dieses zweiten Enumerators wird dann kopiert undenumerator3
durch die markierte Anweisung Nr. 3 erweitert. Dann, daenumerator3
undenumerator4
beide Referenztypen sind, wird eine REFERENCE toenumerator3
kopiertenumerator4
, sodass die markierte Anweisung beideenumerator3
und effektiv weiterentwickeltenumerator4
.Einige Leute versuchen vorzutäuschen, dass Werttypen und Referenztypen beide Arten von sind
Object
, aber das ist nicht wirklich wahr. Realwerttypen können in konvertiert werdenObject
, sind jedoch keine Instanzen davon. Eine Instanz,List<String>.Enumerator
die an einem Speicherort dieses Typs gespeichert ist, ist ein Werttyp und verhält sich wie ein Werttyp. Wenn Sie es an einen Ort des Typs kopieren,IEnumerator<String>
wird es in einen Referenztyp konvertiert und es verhält sich wie ein Referenztyp . Letzteres ist eine ArtObject
, Ersteres jedoch nicht.Übrigens noch ein paar Anmerkungen: (1) Im Allgemeinen sollten bei veränderbaren Klassentypen die
Equals
Methoden die Referenzgleichheit testen, aber es gibt keine vernünftige Möglichkeit für eine Box-Struktur, dies zu tun. (2) ist trotz seines NamensValueType
ein Klassentyp, kein Werttyp; Alle von abgeleiteten TypenSystem.Enum
sind Werttypen, ebenso wie alle Typen, von denenValueType
mit Ausnahme von abgeleitet istSystem.Enum
, aber beideValueType
undSystem.Enum
Klassentypen.quelle
Strukturen werden als Werttypen implementiert und Klassen sind Referenztypen. Wenn Sie eine Variable vom Typ Foo haben und eine Instanz von Fubar darin speichern, wird sie in einen Referenztyp "verpackt", wodurch der Vorteil der Verwendung einer Struktur in erster Linie zunichte gemacht wird.
Der einzige Grund, warum ich sehe, dass eine Struktur anstelle einer Klasse verwendet wird, ist, dass es sich um einen Werttyp und nicht um einen Referenztyp handelt, die Struktur jedoch nicht von einer Klasse erben kann. Wenn die Struktur eine Schnittstelle erbt und Sie Schnittstellen weitergeben, verlieren Sie diesen Werttyp der Struktur. Könnte es auch einfach zu einer Klasse machen, wenn Sie Schnittstellen benötigen.
quelle
(Nun, ich habe nichts Wichtiges hinzuzufügen, aber noch keine Bearbeitungsfähigkeiten, also geht es weiter.)
Perfekt sicher. Nichts Illegales beim Implementieren von Schnittstellen auf Strukturen. Sie sollten sich jedoch fragen, warum Sie dies tun möchten.
Wenn Sie jedoch einen Schnittstellenverweis auf eine Struktur erhalten, wird diese BOX . Also Leistungseinbußen und so weiter.
Das einzig gültige Szenario, an das ich momentan denken kann, ist in meinem Beitrag hier dargestellt . Wenn Sie den in einer Sammlung gespeicherten Status einer Struktur ändern möchten, müssen Sie dies über eine zusätzliche Schnittstelle tun, die in der Struktur verfügbar gemacht wird.
quelle
Int32
an eine Methode übergeben wird, die einen generischen Typ akzeptiertT:IComparable<Int32>
(der entweder ein generischer Typparameter der Methode oder die Klasse der Methode sein kann), kann diese Methode dieCompare
Methode für das übergebene Objekt verwenden, ohne sie einzuschließen.Ich denke, das Problem ist, dass es Boxen verursacht, weil Strukturen Werttypen sind, so dass es eine leichte Leistungseinbuße gibt.
Dieser Link deutet darauf hin, dass möglicherweise andere Probleme damit vorliegen ...
http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
quelle
Eine Struktur, die eine Schnittstelle implementiert, hat keine Konsequenzen. Zum Beispiel implementieren die eingebauten Systemstrukturen Schnittstellen wie
IComparable
undIFormattable
.quelle
Es gibt kaum einen Grund für einen Werttyp, eine Schnittstelle zu implementieren. Da Sie einen Werttyp nicht unterordnen können, können Sie ihn immer als konkreten Typ bezeichnen.
Wenn Sie natürlich nicht mehrere Strukturen haben, die alle dieselbe Schnittstelle implementieren, ist dies möglicherweise nur geringfügig nützlich, aber an diesem Punkt würde ich empfehlen, eine Klasse zu verwenden und es richtig zu machen.
Wenn Sie eine Schnittstelle implementieren, boxen Sie die Struktur natürlich so, dass sie sich jetzt auf dem Heap befindet und Sie sie nicht mehr als Wert übergeben können ... Dies bestätigt meine Meinung, dass Sie nur eine Klasse verwenden sollten in dieser Situation.
quelle
IComparable
um den Wert zu boxen. Durch einfaches Aufrufen einer Methode, dieIComparable
mit einem Wertetyp erwartet , der sie implementiert, wird der Werttyp implizit eingerahmt.IComparable<T>
für Strukturen vom TypT
ohne Boxing aufgerufen werden.Strukturen sind wie Klassen, die im Stapel leben. Ich sehe keinen Grund, warum sie "unsicher" sein sollten.
quelle