2010-10-04 18 views
13

In C#, wenn Sie ein struct wie so:C#: Warum brechen Mutationen in Readonly-Strukturen nicht?

struct Counter 
{ 
    private int _count; 

    public int Value 
    { 
     get { return _count; } 
    } 

    public int Increment() 
    { 
     return ++_count; 
    } 
} 

Und Sie haben ein Programm wie so:

static readonly Counter counter = new Counter(); 

static void Main() 
{ 
    // print the new value from the increment function 
    Console.WriteLine(counter.Increment()); 
    // print off the value stored in the item 
    Console.WriteLine(counter.Value); 
} 

Die Ausgabe des Programms:

1 
0 

Das scheint völlig falsch zu sein. Ich würde entweder erwarten, dass die Ausgabe zwei 1s ist (wie es ist, wenn Counter ist ein class oder wenn struct Counter : ICounter und counter ist ein ICounter) oder ein Kompilierungsfehler sein. Ich weiß, dass es schwierig ist, dies bei der Kompilierung zu erkennen, aber dieses Verhalten scheint die Logik zu verletzen.

Gibt es einen Grund für dieses Verhalten jenseits der Implementierungsschwierigkeiten?

+10

Siehe Eric Lippert ["Mutating Readonly Structs"] (http://blogs.msdn.com/b/ericlippert/archive/2008/05/14/mutating-readonly-structs.aspx) –

+0

* Warum * würden Sie erwarte zwei 1s? Du hast gesagt, du wolltest, dass es nur gelesen wird, also warum willst du, dass es sich ändert? –

Antwort

8

structs sind Werttypen und haben daher einen Werttyp sematics. Dies bedeutet, dass Sie bei jedem Zugriff auf die Struktur grundsätzlich mit einer Kopie des Wertes der Struktur arbeiten.

In Ihrer Probe ändern Sie nicht das Original struct, sondern nur eine temporäre Kopie davon.

Sehen Sie hier für weitere Erklärungen:

Why are mutable structs evil

+4

Aber wenn Sie die 'readonly' wegnehmen, dann passiert die erwartete Ausgabe von zwei 1s. Es macht also eindeutig keine Kopie in diesem Fall. Bedeutet dies, dass alle auf readonly-Strukturen aufgerufenen Funktionen Kopien der Struktur erzeugen (da C# kein Konzept für eine Mutationsfunktion hat)? –

+4

Ja, das ist gemäß Abschnitt 7.5.4 der C# -Sprachenspezifikation der Fall. Siehe Eric Lipperts Beitrag zu diesem Thema, um weitere Details zu erhalten. –

+1

Eine unglückliche Designschwäche in .net besteht darin, dass es keine Mittel gibt, mit denen struct-Methoden anzeigen können, ob sie 'this' modifizieren oder nicht. Da wäre es ärgerlich, wenn man bei Read-Only-Strukturen keine Methoden oder Eigenschaften verwenden könnte, und es wäre auch ärgerlich, wenn 'readonly'-Modifikatoren auf Strukturen nicht berücksichtigt würden, C# und vb.net stattdessen" kompromittieren ", indem Methodenaufrufe auf schreibgeschützte Strukturen machen eine Kopie der Struktur vor dem Aufruf der Methode. Beachten Sie, dass der Compiler beim direkten Verfügbarmachen von Strukturfeldern in der Lage ist, Lesezugriffe von Schreibvorgängen zu unterscheiden, und ... – supercat

3

In .net, eine Struktur Instanzmethode ist semantisch äquivalent zu einer statischen Struktur Methode mit einem extra ref Parameter der Struktur Art. Somit wird die Erklärungen gegeben:

struct Blah { 
    public int value; 
    public void Add(int Amount) { value += Amount; } 
    public static void Add(ref Blah it; int Amount; it.value += Amount;} 
} 

Die Methode ruft:

someBlah.Add(5); 
Blah.Add(ref someBlah, 5); 

sind semantisch äquivalent, mit Ausnahme von einem Unterschied: die letztere Anruf wird nur dann zugelassen werden, wenn someBlah eine veränderbare Speicherstelle (variable, Feld usw.) und nicht, wenn es sich um einen Nur-Lese-Speicherort oder um einen temporären Wert handelt (Ergebnis des Lesens einer Eigenschaft usw.).

Dies stellte die Designer von .net-Sprachen mit einem Problem konfrontiert: die Verwendung von Member-Funktionen in schreibgeschützten Strukturen zu verbieten wäre ärgerlich, aber sie wollten nicht zulassen, dass Member-Funktionen auf schreibgeschützte Variablen schreiben. Sie entschieden sich für "punt" und machen es so, dass das Aufrufen einer Instanzmethode für eine schreibgeschützte Struktur eine Kopie der Struktur erstellt, die Funktion darauf aufruft und sie dann verwirft. Dies hat zur Folge, dass Aufrufe von Instanzmethoden, die die zugrunde liegende Struktur nicht schreiben, verlangsamt werden und dass der Versuch, eine Methode zu verwenden, die die zugrundeliegende Struktur in einer schreibgeschützten Struktur aktualisiert, verschiedene gebrochene Semantiken ergibt von was würde erreicht werden, wenn es die Struktur direkt übergeben würde. Beachten Sie, dass die zusätzliche Zeit, die die Kopie benötigt, in Fällen, die ohne die Kopie nicht korrekt gewesen wären, fast nie eine korrekte Semantik ergibt.

Einer meiner wichtigsten peeves in .net ist, dass es immer noch (seit mindestens 4.0 und wahrscheinlich 4.5) immer noch kein Attribut gibt, über das eine Strukturelementfunktion angeben kann, ob es this ändert.Leute schildern, wie Strukturen unveränderlich sein sollten, anstatt die Werkzeuge bereitzustellen, die es Strukturen erlauben, Mutationsmethoden sicher anzubieten. Dies trotz der Tatsache, dass sogenannte "unveränderliche" Strukturen eine Lüge sind. Alle nicht-trivialen Werttypen in veränderbaren Speicherorten sind veränderbar, ebenso wie alle eingerahmten Werttypen. Eine Struktur "unveränderlich" zu machen, kann einen dazu zwingen, eine ganze Struktur umzuschreiben, wenn man nur ein Feld ändern will, aber seit struct1 = struct2 muate struct1 durch Kopieren aller öffentlichen Felder und private von struct2, und es gibt nichts die Typdefinition für die Struktur tun können, um dies zu verhindern (außer keine Felder zu haben), tut es nichts, um eine unerwartete Mutation von Strukturelementen zu verhindern. Darüber hinaus sind Strukturen aufgrund von Threading-Problemen sehr eingeschränkt in ihrer Fähigkeit, jede Art von invarianter Beziehung zwischen ihren Feldern zu erzwingen. IMHO, es wäre im Allgemeinen besser für eine Struktur mit willkürlichen Feldzugriff zu ermöglichen, klar, dass jeder Code erhalten eine Struktur muss überprüfen, ob seine Felder alle erforderlichen Bedingungen erfüllen, als versuchen, die Bildung von Strukturen, die Bedingungen nicht erfüllen zu verhindern.

+0

* '" struct1 = struct2 "mutiert struct1 durch Kopieren von" * "- das stimmt, aber gibt es irgendwelche praktischen Probleme damit (außer dass es im Allgemeinen nicht atomar ist)? Sowohl "struct1" als auch "struct2" sind Variablen oder Felder, die mit tatsächlichen Daten gefüllt sind, so dass das Kopieren in diesem Fall erwartet werden sollte, da die Strukturzuweisung in C und C++ standardmäßig so funktioniert. Wenn "struct1" kein "readonly" -Feld ist, wird es in der Kompilierzeit fehlschlagen. – Groo

+0

Wie auch immer, +1 für hilfreiche Informationen, aber ich würde den Teil leicht umformulieren, wo du sagst, dass * das Aufrufen einer Instanzmethode für eine schreibgeschützte Struktur eine Kopie der Struktur * ergibt, weil es das * Zugreifen auf a 'readonly ist 'struct field erstellt eine Kopie dieser Struktur. Wenn Sie den Wert zuerst in eine lokale Variable einlesen, können Sie ihn mutieren (solange seine inneren Felder änderbar sind) und .NET wird direkt auf der Stack-basierten Struktur arbeiten ("Implementierungsdetails", ich weiß). Wenn ein Feld nicht "readonly" ist, können Methoden direkt mit dem Feld arbeiten, ohne dass es kopiert werden muss. – Groo

+0

@Groo: Es gibt nicht viele Eckfälle, wo es wichtig ist, dass 'struct1 = struct2' das * existing * struct1 mutiert, indem es alle seine Felder überschreibt, aber der einzige Weg, diese Eckfälle richtig zu verstehen, besteht darin, zu verstehen, was ist tatsächlich passiert. In Bezug auf Ihren zweiten Punkt, ich bin nicht ganz klar, was Sie vorschlagen; Der Ausdruck "Nur-Lese-Struktur" bezieht sich sowohl auf Variablen, die ein Nur-Lese-Qualifikationsmerkmal haben, als auch auf temporäre Speicherstellen wie beispielsweise Funktions- oder Eigenschaftsrückgabewerte. muss ich beachten, dass die Rückgabewerte von gerade lesen/schreiben Eigenschaften schreibgeschützt sind? – supercat