Sind statische Mitglieder einer generischen Klasse an die bestimmte Instanz gebunden?

83

Dies ist eher eine Dokumentation als eine echte Frage. Dies scheint noch nicht auf SO angesprochen worden zu sein (es sei denn, ich habe es verpasst).

Stellen Sie sich eine generische Klasse vor, die ein statisches Element enthält:

class Foo<T> {
    public static int member;
}

Gibt es eine neue Instanz des Mitglieds für jede bestimmte Klasse oder gibt es nur eine einzige Instanz für alle Klassen vom Typ Foo?

Es kann leicht durch Code wie folgt verifiziert werden:

Foo<int>.member = 1;
Foo<string>.member = 2;
Console.WriteLine (Foo<int>.member);

Was ist das Ergebnis und wo ist dieses Verhalten dokumentiert?

Mafu
quelle
4
Kurze Antwort: Es gibt eine neue Instanz für jede tatsächliche Klasse, dh eine für jeden verwendeten Typ T( Foo<int>und Foo<string>stellen zwei verschiedene Klassen dar und haben jeweils eine Instanz, aber mehrere Intanzen von Foo<int>teilen sich eine einzelne Instanz von member). Ein detaillierteres Beispiel finden Sie unter: stackoverflow.com/a/38369256/336648
Kjartan

Antworten:

90

Ein staticFeld wird von allen Instanzen desselben Typs gemeinsam genutzt . Foo<int>und Foo<string>sind zwei verschiedene Typen. Dies kann durch die folgende Codezeile bewiesen werden:

// this prints "False"
Console.WriteLine(typeof(Foo<int>) == typeof(Foo<string>));

Wo dies dokumentiert ist, finden Sie in Abschnitt 1.6.5 Felder der C # -Sprachenspezifikation (für C # 3):

Ein statisches Feld identifiziert genau einen Speicherort. Unabhängig davon, wie viele Instanzen einer Klasse erstellt werden, gibt es immer nur eine Kopie eines statischen Felds.

Wie zuvor erläutert; Foo<int>und Foo<string>sind nicht die gleiche Klasse; Es handelt sich um zwei verschiedene Klassen, die aus derselben generischen Klasse aufgebaut sind. Wie dies geschieht, wird in Abschnitt 4.4 des oben genannten Dokuments beschrieben:

Eine generische Typdeklaration an sich bezeichnet einen ungebundenen generischen Typ, der als „Blaupause“ zur Bildung vieler verschiedener Typen verwendet wird, indem Typargumente angewendet werden.

Fredrik Mörk
quelle
26
Hier ist ein Gotcha, wenn Sie sowohl in C # als auch in Java entwickeln. Während Foo<int>und Foo<String>sind verschiedene Typen in C #, sind sie in Java der gleiche Typ, da Java mit Generika umgeht (Typ Lösch- / Compiler-Tricks).
Powerlord
Was wäre, wenn dies der Fall wäre: Foo <int> foo1 = new Foo <int> (); foo1.member = 10; Foo <int> foo2 = new Foo <int> (); foo2.member = 20; was geschieht hier?
Jeder
@Jeder wird der Wert des Mitglieds für beide Instanzen geändert (und ist 20), da sie denselben Typ Foo <int> haben.
Stas Ivanov
1
Was ist, wenn Sie eine nicht generische Basisklasse mit einem statischen Element in eine abgeleitete generische Klasse erben? Wird das noch wahr sein?
Brain2000
@StasIvanov, foo1.member bleibt 10 und foo2.member wird 20. Bitte überprüfen
SHRI
16

Das Problem hierbei ist eigentlich die Tatsache, dass "generische Klassen" überhaupt keine Klassen sind.

Generische Klassendefinitionen sind nur Vorlagen für Klassen, und bis ihre Typparameter angegeben sind, sind sie nur ein Textstück (oder eine Handvoll Bytes).

Zur Laufzeit kann ein Typparameter für die Vorlage angegeben werden, um sie zum Leben zu erwecken und eine Klasse des jetzt vollständig angegebenen Typs zu erstellen. Aus diesem Grund sind statische Eigenschaften nicht vorlagenweit, und deshalb können Sie nicht zwischen List<string>und umwandeln List<int>.

Diese Beziehung spiegelt irgendwie die Klassen-Objekt-Beziehung wider. So wie Klassen nicht existieren *, bis Sie ein Objekt aus ihnen instanziieren, existieren generische Klassen nicht, bis Sie eine Klasse basierend auf der Vorlage erstellen.

PS Es ist durchaus möglich zu deklarieren

class Foo<T> {
    public static T Member;
}

Daraus ergibt sich, dass die statischen Elemente nicht gemeinsam genutzt werden können, da T für verschiedene Spezialisierungen unterschiedlich ist.

SWeko
quelle
4

Sie werden nicht geteilt. Sie sind sich nicht sicher, wo es dokumentiert ist, aber die Analysewarnung CA1000 ( Deklarieren Sie keine statischen Elemente für generische Typen ) warnt davor, da das Risiko besteht, dass der Code komplizierter wird.

Hans Olsson
quelle
1
Wow, noch eine Regel, mit der ich bei FX Cop nicht klar kommen würde.
Matthew Whited
Es ist hier
NtFreX
3

Die C # -Implementierung von Generika ist näher an C ++. In beiden Sprachen MyClass<Foo>und MyClass<Bar>nicht in statischen Elementen, aber in Java. In C # und C ++ MyClass<Foo>wird beim Kompilieren intern ein völlig neuer Typ erstellt, als wären Generika Makros. Normalerweise können Sie ihre generierten Namen in der Stapelverfolgung wie MyClass'1und sehen MyClass'2. Aus diesem Grund teilen sie keine statischen Variablen. In Java werden Generika durch eine einfachere Methode implementiert, bei der der Compiler Code unter Verwendung nicht generischer Typen generiert und überall Typumwandlungen hinzufügt. So MyClass<Foo>und MyClass<Bar>erzeugen keine zwei völlig neue Klasse in Java, anstatt sie beide derselben Klasse sind MyClassdarunter und deshalb sind sie statische Variablen teilen.

Shital Shah
quelle
1

Sie werden nicht wirklich geteilt. Weil das Mitglied überhaupt nicht zur Instanz gehört. Ein statisches Klassenmitglied gehört zur Klasse selbst. Wenn Sie also MyClass.Number haben, ist dies für alle MyClass.Number-Objekte gleich, da es nicht einmal vom Objekt abhängt. Sie können MyClass.Number sogar ohne Objekt aufrufen oder ändern.

Da Foo <int> jedoch nicht dieselbe Klasse wie Foo <string> ist, werden diese beiden Zahlen nicht gemeinsam genutzt.

Ein Beispiel, um dies zu zeigen:

TestClass<string>.Number = 5;
TestClass<int>.Number = 3;

Console.WriteLine(TestClass<string>.Number);  //prints 5
Console.WriteLine(TestClass<int>.Number);     //prints 3
Markierungen
quelle
-4

IMO, du musst es testen, aber ich denke das

Foo<int>.member = 1;
Foo<string>.member = 2;
Console.WriteLine (Foo<int>.member);

wird ausgegeben, 1weil ich denke, dass der Compilator während der Kompilierung 1 Klasse für jede generische Klasse erstellt, die Sie verwenden (in Ihrem Beispiel: Foo<int>und Foo<string>).

Aber ich bin nicht 100% sicher =).

Bemerkung: Ich denke, es ist weder ein gutes Design noch eine gute Praxis, solche statischen Attribute zu verwenden.

Clement Herreman
quelle
6
Wenn nicht 100% sicher - nicht kommentieren
sll