4 Stimmen

Wie erfasste Werte in anonymen Methoden in .NET implementiert werden

Ich bin neugierig auf die tatsächliche .NET-Implementierung und die Entscheidung, die dahinter steckt.

In Java zum Beispiel müssen alle erfassten Werte, die in anonymen Klassen verwendet werden, final sein. Diese Anforderung scheint in .NET entfallen zu sein.

Gibt es auch einen Unterschied in einer Implementierung von erfassten Werten für Werttypen im Gegensatz zu Referenztypen?

Gracias

9voto

Jon Skeet Punkte 1325502

Der einfachste Weg, um herauszufinden, wie es umgesetzt wird, ist, es auszuprobieren. Schreiben Sie einen Code, der eine erfasste Variable verwendet, kompilieren Sie ihn, und schauen Sie ihn sich dann in Reflektor . Beachten Sie, dass es sich um die variable die erfasst wird, und nicht die Wert . Das ist einer der großen Unterschiede zwischen Java und C# in diesem Bereich.

Die Grundidee ist, dass jede Ebene des Geltungsbereichs, die mindestens eine erfasste Variable enthält, zu einer neuen Klasse mit Feldern für die erfassten Variablen führt. Wenn es mehr als eine Ebene gibt, hat ein innerer Bereich auch ein Feld für den nächsthöheren Bereich und so weiter. Die echten lokalen Variablen auf dem Stack sind schließlich Verweise auf Instanzen der automatisch generierten Klassen.

Hier ist ein Beispiel:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        for (int i=0; i < 5; i++)
        {
            int copyOfI = i;

            for (int j=0; j < 5; j++)
            {
                int copyOfJ = j;

                actions.Add(delegate
                {
                    Console.WriteLine("{0} {1}", copyOfI, copyOfJ);
                });
            }
        }

        foreach (Action action in actions)
        {
            action();
        }        
    }
}

(Sie erhalten andere Ergebnisse, wenn Sie keine Kopie nehmen - experimentieren Sie!) Dies wird wie folgt in Code übersetzt:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        for (int i=0; i < 5; i++)
        {
            OuterScope outer = new OuterScope();
            outer.copyOfI = i;

            for (int j=0; j < 5; j++)
            {
                InnerScope inner = new InnerScope();
                inner.outer = outer;
                inner.copyOfJ = j;

                actions.Add(inner.Action);
            }
        }

        foreach (Action action in actions)
        {
            action();
        }        
    }

    class OuterScope
    {
        public int copyOfI;
    }

    class InnerScope
    {
        public int copyOfJ;
        public OuterScope outer;

        public void Action()
        {
            Console.WriteLine("{0} {1}", outer.copyOfI, copyOfJ);
        }
    }
}

Alle Der Verweis auf die erfasste Variable geht schließlich durch die Instanz der generierten Klasse, es handelt sich also nicht nur um eine einmalige Kopie. (Okay, in diesem Fall verwendet nichts anderes im Code die erfassten Variablen, aber Sie können sich leicht vorstellen, dass das möglich ist). Beachten Sie, dass bei jeder Iteration der äußeren Schleife die fünf neuen Instanzen sich alle eine Instanz von OuterScope . Sie könnten versuchen, mit zusätzlichem Code im Delegaten zu spielen, um zu sehen, wie sich das auswirkt - wenn der Delegat sich ändert copyofI diese Änderung wird im nächsten Delegierten sichtbar; Änderungen an copyOfJ wird nicht gesehen, weil der nächste Delegierte eine separate Instanz von InnerScope .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X