Warum Einfangen einer wandelbaren Struktur Variable innerhalb einer Schließung innerhalb einer using-Anweisung Änderung seiner lokalen Verhalten?

StackOverflow https://stackoverflow.com/questions/4642665

Frage

Aktualisieren : Nun, jetzt habe ich gegangen, und es getan: Ich eingereicht einen Fehlerbericht mit Microsoft darüber, wie ich ernsthaft Zweifel daran, dass es das richtige Verhalten ist. Das heißt, ich bin immer noch nicht zu 100% sicher, was in Bezug auf glauben, diese Frage ; so ich sehen kann, dass das, was „richtig“ ist offen für einig Interpretationsebene.

Mein Gefühl ist, dass entweder Microsoft wird akzeptieren, dass dies ein Fehler ist, oder sonst reagiert, dass die Änderung eines wandelbaren Werttyp Variable innerhalb einer using Anweisung nicht definiertes Verhalten darstellt.

Auch für das, was es wert ist, habe ich zumindest ein erraten , was hier geschieht. Ich nehme an, daß der Compiler eine Klasse für den Verschluss erzeugt, „Heben“ die lokale Variable auf eine Instanz dieser Klasse Feld; und da es innerhalb eines using Block ist, es macht das Feld readonly . Wie LukeH wies darauf hin, in einen Kommentar zu der anderen Frage , diese solche Methode ruft als MoveNext aus Modifizieren des Feldes selbst verhindern würde (würde sie stattdessen eine Kopie beeinflussen).


Hinweis: Ich habe diese Frage für die Lesbarkeit gekürzt, obwohl es noch nicht genau kurz ist. Für die Original (mehr) Frage in seiner Gesamtheit, finden Sie in der Versionsgeschichte.

Ich habe durch das, was ich glaube, dass die entsprechenden Abschnitte des ECMA-334 und kann nicht scheinen, eine schlüssige Antwort auf diese Frage zu finden. Ich werde die Frage zuerst angeben, dann für diejenigen, die einen Link zu einigen Bemerkungen hinzufügen, die interessiert sind.

Frage

Wenn ich einen wandelbaren Werttyp habe, dass Geräte IDisposable, kann ich (1) nennen, ein Verfahren, dass ändert den Zustand der lokalen Variablen des Wertes innerhalb einer using Rechnung und Code verhält sich wie ich erwarte. Sobald ich die betreffende Variable in einem Verschluss erfassen in die using Aussage jedoch (2) Änderungen den Wert nicht mehr sichtbar ist im lokalen Bereich.

Dieses Verhalten ist nur scheinbar in dem Fall, in dem der Variable innerhalb des Verschlusses aufgenommen wird und innerhalb einer using Aussage; es ist nicht ersichtlich, wenn nur ein (using) oder der andere Bedingung (Verschluss) vorhanden ist.

Warum in einem Verschluss eine Variable eines wandelbaren Werttyp ist die Erfassung innerhalb einer using Anweisung Änderung seiner lokalen Verhalten?

Im Folgenden sind Codebeispiele veranschaulichen die Punkte 1 und 2. Beide Beispiele werden die folgenden Demonstration Mutable Werttyp verwenden:

struct Mutable : IDisposable
{
    int _value;
    public int Increment()
    {
        return _value++;
    }

    public void Dispose() { }
}

1. Mutieren einen Werttyp Variable innerhalb eines using Block

using (var x = new Mutable())
{
    Console.WriteLine(x.Increment());
    Console.WriteLine(x.Increment());
}

Die Ausgangscode-Ausgänge:

0
1

2. Erfassen eines Werttyp Variable innerhalb einer Schließung innerhalb eines using Block

using (var x = new Mutable())
{
    // x is captured inside a closure.
    Func<int> closure = () => x.Increment();

    // Now the Increment method does not appear to affect the value
    // of local variable x.
    Console.WriteLine(x.Increment());
    Console.WriteLine(x.Increment());
}

Die oben genannten Code-Ausgänge:

0
0

Weitere Kommentare

Es wird festgestellt, dass der Mono Compiler das Verhalten bietet erwarte ich (Änderungen auf den Wert der lokalen Variablen sind noch sichtbar in dem using + Verschluss Fall). Ob dieses Verhalten korrekt ist oder nicht, ist mir unklar.

Für einige mehr meiner Gedanken zu diesem Thema finden Sie unter hier .

War es hilfreich?

Lösung

Es ist ein bekannter Fehler; entdeckten wir es vor ein paar Jahren. Das Update wäre möglicherweise brechen, und das Problem ist ziemlich dunkel; dies sind Punkte gegen sie zu fixieren. Deshalb hat es nie hoch genug, um tatsächlich fixiert priorisiert worden.

Das ist nun seit ein paar Jahren in meiner Warteschlange potenziellen Blog Themen gewesen; vielleicht sollte ich es schreiben.

Und nebenbei bemerkt, Ihre Vermutung bezüglich des Mechanismus, der den Bug erklärt ist ganz richtig; schön psychische Debuggen gibt.

Also, ja, bekannte Fehler, aber danke für den Bericht unabhängig!

Andere Tipps

Das hat mit der Art und Weise Verschlusstypen generiert und verwendet zu tun. Es scheint ein subtiler Fehler in der Art und Weise csc verwendet diese Typen. Zum Beispiel, hier ist die IL durch Monos gmcs erzeugt beim Aufruf von Movenext ():

      IL_0051:  ldloc.3
      IL_0052:  ldflda valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Foo/'<Main>c__AnonStorey0'::enumerator
      IL_0057:  call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()

Beachten Sie, dass es das Feld der Adresse ist geladen, die der Aufruf der Methode erlaubt die Instanz des Werttyp auf dem Verschlussobjekt gespeichert zu ändern. Dies ist, was ich halte die richtige Verhalten sein, und diese Ergebnisse in der Liste Inhalt ganz gut aufgezählt werden.

Hier ist, was csc erzeugt:

      IL_0068:  ldloc.3
      IL_0069:  ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Tinker.Form1/'<>c__DisplayClass3'::enumerator
      IL_006e:  stloc.s 5
      IL_0070:  ldloca.s 5
      IL_0072:  call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()

Also in diesem Fall es nimmt eine Kopie des Wertes Typ-Instanz und das Aufrufen der Methode auf der Kopie. Es sollte nicht überraschen, warum das Sie nirgendwo bekommt. Der get_Current () -Aufruf ist ähnlich falsch:

      IL_0052:  ldloc.3
      IL_0053:  ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Tinker.Form1/'<>c__DisplayClass3'::enumerator
      IL_0058:  stloc.s 5
      IL_005a:  ldloca.s 5
      IL_005c:  call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
      IL_0061:  call void class [mscorlib]System.Console::WriteLine(int32)

Da der Zustand des Enumerator Kopieren es ist hat Movenext nicht hatte () aufgerufen, get_Current () anscheinend gibt default(int).

Kurz gesagt: csc erscheint Buggy zu sein. Es ist interessant, dass Mono dieses Recht bekam, während MS.NET nicht!

... Ich würde gerne Jon Skeet Kommentare zu dieser besonderen Kuriosität hören.


In einem Gespräch mit Brajkovic in #mono, bestimmt er, dass die Sprache C # Spezifikation nicht wirklich Detail wie die Verschlusstyp umgesetzt werden soll, noch wie Zugriffe von Einheimischen, die in der Schließung aufgenommen werden sollten erhalten übersetzt. Eine beispielhafte Implementierung in der Spezifikation scheint die „Kopie“ Methode, dass csc Anwendungen zu verwenden. Deshalb ist entweder Compiler Ausgabe entsprechend der Sprachspezifikation korrekt angesehen werden kann, obwohl ich, dass csc sollte zumindest argumentieren würde die lokale zurück zur Schließung Objekt nach dem Methodenaufruf kopieren.

EDIT -. Das ist falsch, ich die Frage nicht genügend sorgfältig gelesen haben

Platzieren der Struktur in einen Verschluss bewirkt eine Zuordnung. Zuordnungen auf Werttypen führen zu einer Kopie des Typs. Also, was passiert ist, Sie eine neue Enumerator<int> schaffen und Current auf diesem enumerator 0 zurück.

using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        List<int> l = new List<int>();
        Console.WriteLine(l.GetEnumerator().Current);
    }
}

Ergebnis: 0

Das Problem ist der Enumerator in einer anderen Klasse gespeichert ist, so dass jede Aktion wird mit einer Kopie des Enumerators arbeiten.

[CompilerGenerated]
private sealed class <>c__DisplayClass3
{
    // Fields
    public List<int>.Enumerator enumerator;

    // Methods
    public int <Main>b__1()
    {
        return this.enumerator.Current;
    }
}

public static void Main(string[] args)
{
    List<int> <>g__initLocal0 = new List<int>();
    <>g__initLocal0.Add(1);
    <>g__initLocal0.Add(2);
    <>g__initLocal0.Add(3);
    List<int> list = <>g__initLocal0;
    Func<int> CS$<>9__CachedAnonymousMethodDelegate2 = null;
    <>c__DisplayClass3 CS$<>8__locals4 = new <>c__DisplayClass3();
    CS$<>8__locals4.enumerator = list.GetEnumerator();
    try
    {
        if (CS$<>9__CachedAnonymousMethodDelegate2 == null)
        {
            CS$<>9__CachedAnonymousMethodDelegate2 = new Func<int>(CS$<>8__locals4.<Main>b__1);
        }
        while (CS$<>8__locals4.enumerator.MoveNext())
        {
            Console.WriteLine(CS$<>8__locals4.enumerator.Current);
        }
    }
    finally
    {
        CS$<>8__locals4.enumerator.Dispose();
    }
}

Ohne das Lambda des Code näher an, was man erwarten würde.

public static void Main(string[] args)
{
    List<int> <>g__initLocal0 = new List<int>();
    <>g__initLocal0.Add(1);
    <>g__initLocal0.Add(2);
    <>g__initLocal0.Add(3);
    List<int> list = <>g__initLocal0;
    using (List<int>.Enumerator enumerator = list.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            Console.WriteLine(enumerator.Current);
        }
    }
}

Spezifische IL

L_0058: ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Machete.Runtime.Environment/<>c__DisplayClass3::enumerator
L_005d: stloc.s CS$0$0001
L_005f: ldloca.s CS$0$0001
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top