2010-11-09 11 views
5

diesen Code-Schnipsel Betrachten und versuchen zu erraten, was y1 und y2 zuWarum geben diese beiden Funktionen nicht den gleichen Wert zurück?

bewerten
static class Extensions 
{ 
    public static Func<T> AsDelegate<T>(this T value) 
    { 
     return() => value; 
    } 
} 
class Program 
{ 
    static void Main(string[] args) 
    { 
     new Program(); 
    } 

    Program() 
    { 
     double x = Math.PI; 

     Func<double> ff = x.AsDelegate(); 
     Func<double> fg =() => x; 

     x = -Math.PI; 

     double y1 = ff(); // y1 = 3.141.. 
     double y2 = fg(); // y2 = -3.141.. 

    } 
} 

Man könnte sagen, -Aha- Doppel ist ein Wert, Typ und so der Wert durch die Extension-Methode zurückgegeben wird, ist eine Kopie der Haupt x. Aber wenn Sie das oben genannte in Delegaten von Klassen ändern, sind die Ergebnisse immer noch anders. Beispiel:

class Foo 
{ 
    public double x; 
} 
    Program() 
    { 
     Foo foo = new Foo() { x=1.0 }; 

     Func<Foo> ff = foo.AsDelegate(); 
     Func<Foo> fg =() => foo; 

     foo = new Foo() { x = -1.0 }; 

     double y1 = ff().x; // y1 = 1.0 
     double y2 = fg().x; // y2 = -1.0 
    } 

Also die beiden Funktionen müssen zwei verschiedene Instanzen der gleichen Klasse zurückgeben. Es ist interessant, zu berücksichtigen, dass ff() trägt mit ihm einen Verweis auf lokale Variable foo, aber fg() nicht und stützt sich auf das, was in ihrem Umfang ist zur Zeit.

Was passiert also, wenn diese beiden Delegaten an andere Teile des Codes weitergegeben werden, die keine Sichtbarkeit für die foo Instanz haben? Irgendwie wird die Frage, wer welche Information (Daten) besitzt, immer weniger klar, wenn Erweiterungsmethoden mit Delegierten kombiniert werden.

Antwort

3

ff Captures (bindet) an den Wert von x in dieser Zeile:

Func<double> ff = x.AsDelegate(); 

dagegen fg zum Variablex an dieser Linie bindet: So

Func<double> fg =() => x; 

Wenn sich der Wert x ändert, ist ff nicht infiziert, aber fg ändert sich.

+0

Nod zu dieser Antwort wegen der Kürze + Klarheit + Format. – ja72

+0

Meh ... It a: bindet nicht an einen * Wert * von irgendetwas (außer dem Compiler - erzeugt Capture-Klassen-Instanz), und b: bindet nicht strikt an "x" * überhaupt * - klarer ja, aber ich denke, vielleicht ein bisschen irreführend aufgrund dieser Einfachheit. (Einfachheit ist aber gut) –

+0

@Marc - es ist ein Wurf. Ich dachte mir, dass ich mich darauf konzentrieren sollte, wie es einem Benutzer erscheint, anstatt in die Art und Weise zu tauchen, wie es implementiert wird. – Bevan

2

() => x fängt x-Wert. Eine spezielle Klasse wird vom Compiler erstellt, um Lambdas oder anonyme Delegaten zu behandeln. Jede im Lambda verwendete Variable wird erfasst.

Zum Beispiel, wenn Sie folgenden Code ausführen:

List<Func<int>> list = new List<Func<int>>(); 

    for (int i = 0; i < 5; i++) 
    { 
     list.Add(() => i); 
    } 

    list.ForEach(function => Console.WriteLine(function())); 

Sie werden sehen, dass gedruckte Zahlen gleich sind.

+0

Wenn also function() aufgerufen wird, benutzt es den Wert, den ich zu dieser Zeit habe (außerhalb der Schleife)? Ich dachte, dass der Umfang von "i" endet, wenn die Schleife endet ... – ja72

7

AsDelegate fängt die variable value (der Parameter AsDelegate), während die variable () => xx einfängt. Wenn Sie also den Wert x ändern, gibt der Lambda-Ausdruck einen anderen Wert zurück. x Ändern nicht value allerdings ändern.

Siehe: Outer Variable Trap

+0

Streng genommen erfasst es den Wert * Argument *; deren Wert ist anfänglich der Wert von x und wird nie geändert. Eine feine Unterscheidung ... –

3

Die AsDelegate Erweiterungsmethode verwendet den Wert von x zum Zeitpunkt AsDelegate während des Lambda-Ausdrucks bezeichnet wird erfaßt () => x die Variablen x und daher der Wert von x ist an der Zeit, um die Expression aufgerufen wird (eher als der Wert, wenn es wurde definiert).

4

In AsDelegate, erobern wir das Argument "Wert". Der Wert dieses Arguments wird als Kopie des Werts der Variablen zum Zeitpunkt des Aufrufs der Methode genommen und ändert sich nie - so sehen wir das ursprüngliche Objekt.

Die direkte Lambda fängt die Variable foo (nicht Wert der Variablen - die Variable selbst) - so wir Änderungen an dieser sehen tun.

im Grunde ein Methodenaufruf Zugabe änderte sich die Oberschenkel eingefangen zu werden.

0

Die erste Methode erstellt einen neuen Delegaten zu einer bestimmten Funktion und speichert sie. Wenn Sie foo später überschreiben, wird Ihr neu erstellter Delegat nicht berührt.

Das zweite ist ein lambda Ausdruck, der Aufzüge/* * einfängt seinen Zusammenhang, dass die variable foo bedeutet. Alle Änderungen an Variablen, die durch Lambda-Ausdrücke aufgehoben wurden, werden von diesem Lambda-Ausdruck erkannt.

Verwandte Themen