Frage

ich mich zu erinnern, etwas darüber zu lesen, wie es ist schlecht für structs über C # Schnittstellen in CLR zu implementieren, aber ich kann nicht scheinen, etwas dagegen zu finden. Ist es schlimm? Gibt es unbeabsichtigte Folgen, dies zu tun?

public interface Foo { Bar GetBar(); }
public struct Fubar : Foo { public Bar GetBar() { return new Bar(); } }
War es hilfreich?

Lösung

Es gibt mehrere Dinge in dieser Frage geht ...

Es ist möglich, eine Struktur eine Schnittstelle zu implementieren, aber es gibt Bedenken, die etwa mit Gießen, Wandelbarkeit und Leistung kommen. Sehen Sie diesen Beitrag für weitere Informationen: http: //blogs.msdn. com / abhinaba / Archiv / 2005/10/05 / 477238.aspx

Im Allgemeinen structs sollte für Objekte verwendet werden, die Wert-Typ Semantik haben. eine Schnittstelle auf einer Struktur Durch die Implementierung können Sie in Box Bedenken laufen, wie die Struktur hin und her zwischen der Struktur und der Schnittstelle umgewandelt wird. Als Ergebnis der Box-Operationen, die den internen Zustand der Struktur verhalten sich möglicherweise nicht ordnungsgemäß geändert werden.

Andere Tipps

Da sonst niemand explizit diese Antwort zur Verfügung gestellt ich folgendes fügt:

Implementieren eine Schnittstelle auf eine Struktur hat keine negativen Folgen auch immer.

Alle Variable der Interface-Typ verwendet, um eine Struktur zu halten, wird in einem Box-Wert dieser Struktur führen verwendet wird. Wenn die Struktur unveränderlich ist (eine gute Sache), dann ist dies im schlimmsten Fall ein Performance-Problem, es sei denn Sie sind:

  • unter Verwendung des resultierenden Objekt zum Verriegeln (eine ungeheuer schlechte Idee, eine Möglichkeit)
  • mit Verweis Gleichheit Semantik und erwartet, dass es für zwei geschachtelte Werte aus der gleichen Struktur zu arbeiten.

Beide wäre unwahrscheinlich, stattdessen werden Sie wahrscheinlich eine der folgenden zu tun:

Generics

Vielleicht sind viele vernünftige Gründe für structs Schnittstellen Implementierung ist, so dass sie mit Einschränkungen . Wenn auf diese Weise die Variable verwendet, etwa so:

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);
    }
}
  1. Aktivieren Sie die Verwendung der Struktur als Typ-Parameter
    • solange keine andere Einschränkung wie new() oder class verwendet wird.
  2. Lassen Sie die Vermeidung von Boxen auf structs auf diese Weise verwendet.

Dann ist this.a keine Schnittstelle Referenz so ist es nicht eine Schachtel verursachen, was in sie gesetzt wird. Des Weiteren, wenn der c # Compiler kompiliert die generische Klassen und Instanzen der Parameter Type T definiert muss Anrufungen der Instanzmethoden einlegen, damit die gezwungen Opcode:

  

Wenn thisType ist ein Wert, Art und thisType implementiert Methode dann PTR wird weitergegeben unmodifizierten als ‚diesen‘ Zeiger auf eine Aufrufmethode Instruktion, für die Durchführung des Verfahrens nach thisType.

Dies vermeidet die Boxen und da der Wert Typ die Schnittstelle implementiert ist muss implementieren die Methode, so wird kein Boxen auftreten. Im obigen Beispiel wird der Equals() Aufruf ohne Box getan auf this.a 1 .

Niedrige Reibung APIs

Die meisten sollten structs haben primitive ähnliche Semantik, wo bitweise identische Werte betrachtet werden gleich 2 . Die Laufzeit wird ein solches Verhalten in dem impliziten Equals() liefert aber langsam sein kann. Auch diese implizite Gleichheit ist nicht als Implementierung von IEquatable<T> ausgesetzt und somit verhindert, dass structs leicht als Schlüssel für die Wörterbücher verwendet werden, sofern sie nicht ausdrücklich es selbst umsetzen. Es ist daher für viele öffentlichen Strukturtypen gemeinsam zu erklären, dass sie IEquatable<T> implementieren (wo T ist sie selbst) als auch diese leichter und leistungsfähigeren zu machen, wie im Einklang mit dem Verhalten vieler bestehenden Werttypen innerhalb der CLR BCL.

Alle Primitiven in der BCL bei einem Minimum implementieren:

  • IComparable
  • IConvertible
  • IComparable<T>
  • IEquatable<T> (und damit IEquatable)

Viele auch IFormattable implementieren, weiterhin viele der System-definierten Wertetypen wie Datum- und Timespan und Guid implementieren viele oder alle diese auch. Wenn Sie einen ähnlich ‚weithin nützlich‘ Typen wie eine komplexe Zahl Struktur oder einige festen Breite Textwert setzen dann viele diese gängigen Schnittstellen (richtig) Implementierung wird Ihre Struktur nützliche und nutzbar machen.

Ausschlüsse

Natürlich, wenn die Schnittstelle impliziert stark Veränderlichkeit (wie ICollection), dann Umsetzung es eine schlechte Idee ist, wie es bedeuten würde, tun Sie entweder die Struktur wandelbar gemacht (was zu dem möglichen Fehlern beschrieben bereits in der Änderungen treten auf dem Box-Wert stattdas Original) oder Sie Benutzer verwirren, indem die Auswirkungen der Methoden wie Add() oder werfen Ausnahmen ignoriert.

Viele Schnittstellen bedeuten nicht Veränderlichkeit (wie IFormattable) und dienen als idiomatische Weise bestimmte Funktionen in einer konsistenten Art und Weise verfügbar zu machen. Oft ist der Benutzer der Struktur wird über alle Boxen Aufwand für ein solches Verhalten nicht.

Zusammenfassung

Wenn vernünftig gemacht, auf unveränderlichen Werttypen, Implementierung von Schnittstellen, ist eine gute Idee,


Weitere Informationen:

1: Beachten Sie, dass der Compiler diese verwenden können, wenn virtuelle Methoden auf Variablen aufrufen, die sind bekannt von einer bestimmten Strukturtyp sein, aber in denen es erforderlich ist eine virtuelle Methode aufzurufen. Zum Beispiel:

List<int> l = new List<int>();
foreach(var x in l)
    ;//no-op

Der Enumerator von der Liste zurückgegeben wird, ist eine Struktur, eine Optimierung eine Zuordnung zu vermeiden, wenn die Liste aufzählt (mit einigen interessanten Folgen ). Doch die Semantik von foreach angeben, dass, wenn der Enumerator implementiert IDisposable dann wird Dispose() einmal aufgerufen werden, die Iteration abgeschlossen ist. Offensichtlich hat dies durch einen Box-Aufruf auftreten würde keinen Nutzen des Enumerators beseitigen, eine Struktur zu sein (in der Tat wäre es schlimmer sein). Schlimmer noch, wenn dispose Aufruf den Zustand des Enumerator in irgendeiner Weise modifiziert dann auf der Box-Instanz das passieren würde und viele subtile Bugs könnten in komplexen Fällen eingeführt werden. Deshalb ist die IL in dieser Art von Situation emittiert wird:

IL_0001:  newobj      System.Collections.Generic.List..ctor
IL_0006:  stloc.0     
IL_0007:  nop         
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:  call        System.Collections.Generic.List.get_Current
IL_0018:  stloc.1     
IL_0019:  ldloca.s    02 
IL_001B:  call        System.Collections.Generic.List.MoveNext
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:  constrained. System.Collections.Generic.List.Enumerator
IL_002E:  callvirt    System.IDisposable.Dispose
IL_0033:  nop         
IL_0034:  endfinally  

So ist die Implementierung von IDisposable verursacht keine Performance-Probleme und die (bedauerliche) wandelbar Aspekt der enumerator erhalten wird, sollte die Dispose-Methode wirklich etwas tun!

2: Doppel und Schwimmer sind Ausnahmen von dieser Regel, wo NaN-Wert nicht gleich betrachtet

.

In einigen Fällen kann es gut sein für eine Struktur eine Schnittstelle zu implementieren (wenn es nicht sinnvoll war, ist es zweifelhaft ist, die Schöpfer von .net haben es würde zur Verfügung gestellt). Wenn ein struct eine Nur-Lese-Schnittstelle wie IEquatable<T> implementiert, die in einer struct Speicherstelle (Variable, Parameter, Array-Element, etc.) vom Typ IEquatable<T> erfordern, dass sie verpackt werden (jeder struct-Typ definiert eigentlich zwei Arten von Dingen: eine Speicherplatztyp, die als Werttyp und ein Heap-Objekttyp verhält, die als Klassentyp verhält, die erste in die zweiten implizit umwandelbar ist - „Box“ - und die zweite können über explizite Umwandlung auf den ersten umgerechnet werden - "Unboxing"). Es ist möglich, eine Struktur der Implementierung einer Schnittstelle ohne Boxen zu nutzen, jedoch mit, was beschränkt Generika genannt werden.

Zum Beispiel, wenn man ein Verfahren CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T> hatte, ein solches Verfahren thing1.Compare(thing2) nennen könnte, ohne dass thing1 oder thing2 boxen. Wenn thing1 passiert zu sein, beispielsweise ein Int32, weiß die Laufzeit, wenn es den Code für CompareTwoThings<Int32>(Int32 thing1, Int32 thing2) erzeugt. Da es die genaue Art des weiß sowohl das, was die Methode Hosting und das Dings, das als Parameter übergeben werden ist, wird es auch nicht von ihnen zu dem Kasten hat.

Das größte Problem mit structs der Schnittstellen implementieren, ist, dass eine Struktur, die in einem Ort von Interface-Typ, Object oder ValueType (im Gegensatz zu einer Position der eigenen Art) gespeichert wird als Klassenobjekt verhalten wird. Für Nur-Lese-Schnittstellen dies nicht generell ein Problem ist, aber für eine Mutieren Schnittstelle wie IEnumerator<T> kann es einige seltsame Semantik ergeben.

Nehmen wir zum Beispiel den folgenden Code:

List<String> myList = [list containing a bunch of strings]
var enumerator1 = myList.GetEnumerator();  // Struct of type List<String>.IEnumerator
enumerator1.MoveNext(); // 1
var enumerator2 = enumerator1;
enumerator2.MoveNext(); // 2
IEnumerator<string> enumerator3 = enumerator2;
enumerator3.MoveNext(); // 3
IEnumerator<string> enumerator4 = enumerator3;
enumerator4.MoveNext(); // 4

So bezeichnete Aussage # 1 wird prime enumerator1 das erste Element zu lesen. Der Zustand des Enumerator wird enumerator2 kopiert werden. Marked Anweisung # 2 wird diese Kopie vorrücken das zweite Element zu lesen, aber keinen Einfluss auf enumerator1. Der Zustand des zweiten enumerator wird dann auf enumerator3 kopiert werden, die 3 durch deutliche Aussage # vorgeschoben wird. Dann, weil enumerator3 und enumerator4 beiden Referenztypen sind, ist ein BEZUG zu enumerator3 wird dann auf enumerator4 kopiert werden, so markierte Anweisung effektiv voran beide enumerator3 und enumerator4.

versuchen einige Leute, die Werttypen und Referenztypen beiden Arten von Object sind, so zu tun, aber das ist nicht wirklich wahr. Tatsächlicher Wert Typen sind konvertierbar Object, sind aber nicht Instanzen davon. Eine Instanz List<String>.Enumerator, die in einer Position dieses Typs gespeichert ist, ist ein Wert-Typ- und verhält sich wie ein Werttyp; es zu einem Ort des Typ IEnumerator<String> Kopieren wird es auf einen Referenztyp umwandeln und wird er als Referenztyp verhalten. Letzteres ist eine Art Object, aber die erstere nicht.

BTW, ein paar mehr Hinweise: (1) Im Allgemeinen wandelbar Klassentypen sollten ihre Equals Methoden Testreferenzgleichheit haben, aber es gibt keine anständige Art und Weise für eine Box-Struktur zu tun; (2) trotz seines Namens ist ValueType eine Klasse-Typ, kein Werttyp; alle Arten von System.Enum sind Werttypen abgeleitet, da alle Typen sind, die sich von ValueType mit Ausnahme von System.Enum ableiten, aber beide ValueType und System.Enum sind Klassentypen.

Structs werden als Werttypen implementiert und Klassen sind Referenztypen. Wenn Sie eine Variable vom Typ Foo haben, und Sie speichern eine Instanz von Fubar darin, es wird „-Box es“ nach oben in einen Referenztyp, also den Vorteil, das Besiegen einer Struktur in erster Linie verwendet wird.

Der einzige Grund, warum ich sehe eine Struktur statt einer Klasse zu verwenden, weil sie ein Werttyp und kein Referenztyp sein, aber die Struktur kann nicht von einer Klasse erben. Wenn Sie die Struktur erben eine Schnittstelle haben, und Sie Schnittstellen laufen um, verlieren Sie diesen Wert Typ Natur der Struktur. Es könnte aber auch nur eine Klasse, wenn Sie Schnittstellen benötigen.

(Nun bekam nichts Besonderes hinzuzufügen, aber die Bearbeitung Fähigkeiten haben noch nicht so geht hier ..)
absolut sicher. Nichts illegal mit Schnittstellen auf structs Umsetzung. Allerdings sollten Sie sich fragen, warum Sie es tun wollen würde.

Allerdings auf eine Struktur eine Interface-Referenz zu erhalten wird BOX es. So Leistungseinbuße und so weiter.

Das einzige gültige Szenario, das ich jetzt denken kann, ist dargestellt in meinem Beitrag hier . Wenn Sie eine Struktur des Staates in einer Sammlung gespeichert ändern möchten, sollten Sie es über eine zusätzliche Schnittstelle an der Struktur ausgesetzt zu tun haben.

Ich denke, das Problem ist, dass es Boxen verursacht, weil structs Werttypen sind so eine leichte Leistungseinbuße ist.

schlägt Dieser Link könnte es andere Probleme mit ihm sein ...

http://blogs.msdn.com/abhinaba /archive/2005/10/05/477238.aspx

Es gibt keine Auswirkungen auf eine Struktur eine Schnittstelle zu implementieren. Zum Beispiel kann das eingebaute System structs implementieren Schnittstellen wie IComparable und IFormattable.

Es gibt sehr wenig Grund für einen Werttyp eine Schnittstelle zu implementieren. Da Sie keinen Werttyp Unterklasse kann, können Sie sich immer auf sie als konkreter beziehen.

Es sei denn natürlich, Sie mehrere Strukturen haben alle die gleiche Schnittstelle implementieren, könnte es dann marginal nützlich sein, aber an diesem Punkt würde ich empfehlen, eine Klasse verwenden und es richtig zu machen.

Natürlich, durch eine Schnittstelle implementiert, Sie sind Boxen, die Struktur, so dass es jetzt sitzt auf dem Heap, und Sie werden es nicht mehr von Wert sein kann passieren ... Das stärkt wirklich meine Meinung, dass Sie sollten nur verwenden, um eine Klasse in dieser Situation.

Structs sind wie Klassen, die in dem Stapel leben. Ich sehe keinen Grund, warum sie „unsicher“ sein.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top