2009-08-01 19 views
77

pensé que estaría bien hacer algo como esto (con la lambda haciendo un retorno de rendimiento):En C#, ¿por qué un método anónimo no puede contener una declaración de rendimiento?

public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new() 
{ 
    IList<T> list = GetList<T>(); 
    var fun = expression.Compile(); 

    var items =() => { 
     foreach (var item in list) 
      if (fun.Invoke(item)) 
       yield return item; // This is not allowed by C# 
    } 

    return items.ToList(); 
} 

Sin embargo, descubrí que no puedo usar el rendimiento en el método anónimo. Me pregunto por qué. El yield docs solo dice que no está permitido.

Como no estaba permitido, acabo de crear una lista y agregué los elementos a ella.

+0

Ahora que podemos tener lambdas '' async' anónimos permitiendo await' interior en C# 5.0, estaría interesado en saber por qué todavía paraíso 'implementado iteradores anónimos con' rendimiento 'dentro. Más o menos, es el mismo generador de máquina de estado. – Noseratio

Respuesta

91

Eric Lippert escribió recientemente una serie de publicaciones en el blog acerca de por qué el rendimiento no está permitido en algunos casos.

Edit2:

  • Part 7(éste fue publicada más tarde y se refiere específicamente a esta pregunta)

Es probable que encontrar la respuesta allí ...


EDIT1: esto se explica en el comentarios de la Parte 5, en la respuesta de Eric al comentario de Abhijeet Patel:

Q:

Eric,

Puede también proporcionar alguna información sobre por qué "rendimientos" no están permitidos dentro de un método anónimo o lambda expresión

A:

Buena pregunta . Me encantaría tener bloques iterativos anónimos. Sería totalmente impresionante para ser capaces de construir mismo un generador de secuencia de poco in situ que se cerró sobre variables locales. El motivo por el que no es es sencillo: los beneficios no superan los costos.La genialidad de generadores de secuencias en el lugar es en realidad bastante pequeño en el gran esquema de cosas y los métodos nominales hacen bien el trabajo en la mayoría de los escenarios . Entonces los beneficios no son tan convincentes como .

Los costos son grandes. Iterador reescritura es el más complicado transformación en el compilador, y reescritura método anónimo es el segundo más complicada. Anónimo métodos pueden ser dentro de otros métodos anónimos y métodos anónimos pueden ser bloques dentro de iterador. Por lo tanto, lo que hacemos es el primero que reescriben todo métodos anónimos para que se conviertan métodos de una clase de clausura. Esto es lo último que hace el compilador antes de emitir IL para un método. Una vez que el paso se hace, el rewriter iterador puede asumir que no hay métodos anónimos en el iterador bloque; todos han sido reescritos ya. Por lo tanto, el iterador reescritura puede concentrarse en reescribiendo el iterador, sin preocupándose de que pueda haber un método anónimo no realizado allí.

Además, los bloques de iterador que nunca "nido", a diferencia de los métodos anónimos. El iterador Rewriter puede suponer que todos los bloques del iterador son de "nivel superior".

Si los métodos anónimos están permitidos en contienen bloques iteradores, entonces ambos esas suposiciones salen por la ventana. Usted puede tener un bloque de iterador que contiene un método anónimo que contiene un método anónimo que contiene un bloque de iterador que contiene un método anónimo, y ... asco. Ahora tenemos que escribir una pase reescritura que puede manejar iterador anidada bloques y métodos anónimos anidados en mismo tiempo, la fusión de la mayoría de nuestros dos complicados algoritmos en un algoritmo mucho más complicado . Sería ser realmente difícil de diseñar, implementar, y probar. Somos lo suficientemente inteligentes como para hacer así que estoy seguro. Tenemos un equipo inteligente aquí. Pero no queremos tomar en esa gran carga para una función "buena para tener pero no es necesaria". - Eric

+0

Interesante, especialmente porque ahora hay funciones locales. – Mafii

2

Lamentablemente no sé por qué no lo permitieron, ya que, por supuesto, es completamente posible imaginar cómo funcionaría.

Sin embargo, los métodos anónimos ya son una pieza de "compilación mágica" en el sentido de que el método será extraído a un método en la clase existente, o incluso a una clase completamente nueva, dependiendo de si trata con local variables o no

Además, los métodos de iterador que usan yield también se implementan utilizando el compilador magic.

Supongo que uno de estos dos hace que el código no sea identificable con la otra pieza de magia, y que se decidió no perder tiempo haciendo que esto funcione para las versiones actuales del compilador de C#. Por supuesto, puede que no sea una elección consciente, y que simplemente no funciona porque nadie pensó en implementarla.

Para una pregunta 100% precisa, le sugiero que utilice el sitio Microsoft Connect e informe una pregunta, estoy seguro de que obtendrá algo utilizable a cambio.

18

Eric Lippert ha escrito una excelente serie de artículos sobre las limitaciones (y las decisiones de diseño que influyen en esas decisiones) en iterator blocks

En particular, los bloques de iterador son implementados por algunas transformaciones de código de compilador sofisticados. Estas transformaciones impactarían con las transformaciones que ocurren dentro de las funciones anónimas o lambdas de tal manera que en ciertas circunstancias ambos tratarían de "convertir" el código en algún otro constructo que fuera incompatible con el otro.

Como resultado, están prohibidas las interacciones.

Cómo se tratan los bloques de iteradores bajo el capó bien here.

Como un ejemplo simple de una incompatibilidad:

public IList<T> GreaterThan<T>(T t) 
{ 
    IList<T> list = GetList<T>(); 
    var items =() => { 
     foreach (var item in list) 
      if (fun.Invoke(item)) 
       yield return item; // This is not allowed by C# 
    } 

    return items.ToList(); 
} 

El compilador es querer al mismo tiempo para convertir esto en algo así como:

// inner class 
private class Magic 
{ 
    private T t; 
    private IList<T> list; 
    private Magic(List<T> list, T t) { this.list = list; this.t = t;} 

    public IEnumerable<T> DoIt() 
    { 
     var items =() => { 
      foreach (var item in list) 
       if (fun.Invoke(item)) 
        yield return item; 
     } 
    } 
} 

public IList<T> GreaterThan<T>(T t) 
{ 
    var magic = new Magic(GetList<T>(), t) 
    var items = magic.DoIt(); 
    return items.ToList(); 
} 

y al mismo tiempo el aspecto iterador está tratando de hacer es trabajo hacer una pequeña máquina de estados. Ciertos ejemplos simples pueden funcionar con una buena cantidad de cordura de control (primera trata de los (cierres posiblemente arbitraria nexted) luego ver si el nivel muy inferior resultante clases podría transformarse en máquinas de estado de iterador.

Sin embargo, esto sería

  1. Mucho trabajo.
  2. No podría funcionar en todos los casos sin que el aspecto del bloque iterador sea capaz de evitar que el aspecto de cierre aplique ciertas transformaciones para la eficiencia (como promover variables locales a variables de instancia en lugar de una clase de cierre completa).
    • Si hubo incluso una ligera posibilidad de solapamiento donde era imposible o no lo suficientemente duro para ser implementado a continuación, el número de problemas de soporte resultantes serían probablemente alta ya que el cambio sutil de ruptura se perdería en muchos usuarios.
  3. Puede ser muy fácil de trabajar alrededor.

En su ejemplo, así:

public IList<T> Find<T>(Expression<Func<T, bool>> expression) 
    where T : class, new() 
{ 
    return FindInner(expression).ToList(); 
} 

private IEnumerable<T> FindInner<T>(Expression<Func<T, bool>> expression) 
    where T : class, new() 
{ 
    IList<T> list = GetList<T>(); 
    var fun = expression.Compile(); 
    foreach (var item in list) 
     if (fun.Invoke(item)) 
      yield return item; 
} 
+1

No hay una razón clara por la que el compilador no pueda, una vez que haya eliminado todos los cierres, realizar la transformación de iterador habitual. ¿Conoces un caso que en realidad presente alguna dificultad? Por cierto, su clase 'Magic' debe ser' Magic '. – Qwertie

1

Me gustaría hacer esto:

IList<T> list = GetList<T>(); 
var fun = expression.Compile(); 

return list.Where(item => fun.Invoke(item)).ToList(); 

Por supuesto que necesita la System.Core.dll referencia de .NET 3.5 para el método de LINQ. E incluyen:

using System.Linq; 

Cheers,

Sly

Cuestiones relacionadas