2010-11-09 13 views
5

Considérese este fragmento de código y tratar de adivinar lo que y1y2 y evaluar a¿Por qué estas dos funciones no devuelven el mismo valor?

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.. 

    } 
} 

Se podría decir -Aha- doble es un tipo de valor por lo que el valor devuelto por el método de extensión es una copia de la principal x. Pero cuando cambias lo anterior en delegados de clases, los resultados aún son diferentes. Ejemplo:

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 
    } 

De modo que las dos funciones deben devolver dos instancias diferentes de la misma clase. Es interesante considerar que ff() lleva consigo una referencia a la variable local foo, pero fg() no y se basa en lo que está actualmente en el alcance.

Entonces, ¿qué sucede cuando estos dos delegados pasan a otras partes del código que no tienen visibilidad en la instancia foo? De alguna manera, la cuestión de quién posee qué información (datos) es cada vez menos clara cuando los métodos de extensión se combinan con los delegados.

Respuesta

3

ff capturas (se une) al valor de x en esta línea:

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

Por el contrario, fg se une a la variable dex en esta línea:

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

Así , cuando el valor de x cambia, ff no se ve afectado, pero fg cambios.

+0

Cabeceo a esta respuesta debido a la brevedad + claridad + formato. – ja72

+0

Meh ... It a: no se une a * valor * de nada (excepto la instancia de clase de captura de generación de compilador), y b: no se vincula estrictamente a "x" * en absoluto * - más claro sí, pero creo que tal vez un poco engañoso debido a esa simplicidad. (la simplicidad es buena) –

+0

@Marc - es un lanzamiento. Pensé en centrarme en la forma en que parece a un usuario, en lugar de sumergirme en la forma en que se implementó. – Bevan

2

() => x captura el valor x. La clase especial es creada por el compilador para manejar lambdas o delegados anónimos, se captura cualquier variable utilizada en el lambda.

Por ejemplo si ejecuta código siguiente:

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

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

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

verá que los números impresos son los mismos.

+0

Entonces, cuando se llama a function(), utiliza cualquier valor que tenga en ese momento (fuera del ciclo)? Pensé que el alcance de 'i' termina cuando termina el ciclo ... – ja72

7

AsDelegate capta la variable value (el parámetro de AsDelegate), mientras que () => x capta la variable x. Por lo tanto, si cambia el valor de x, la expresión lambda devolverá un valor diferente. Cambiar x no cambia value embargo.

Ver: Outer Variable Trap

+0

Estrictamente hablando, captura el valor * argument *; cuyo valor es inicialmente el valor de x, y nunca se cambia. Una sutil distinción ... –

3

El método de extensión AsDelegate utiliza el valor de x en el momento AsDelegate se llama mientras que la expresión lambda () => x capta la variable x y por lo tanto el valor de x se toma en el momento se invoca la expresión (en lugar del valor cuando se Fue definido).

4

En AsDelegate, estamos capturando el argumento "valor". el valor de esta estructura se toma como una copia del valor de la variable en el momento en que se invoca el método, y nunca cambia, por lo que vemos el objeto original.

La lambda directa captura la variable de foo (no el valor de la variable - la propia variable) - por lo tanto nos hacemos ver cambios en este.

Básicamente, agregar una llamada al método cambió el muslo que se está capturando.

0

El primer método crea un nuevo delegado en una función determinada y lo almacena. Más tarde sobrescribe foo, su delegado recién creado no se toca.

El segundo es un expresión lambda que ascensores/* captura * su contexto, que significa que la variable foo. Todos los cambios en las variables que han sido levantadas por las expresiones lambdas son vistas por esa expresión lambda.

Cuestiones relacionadas