typeparam example cref c# yield anonymous-methods yield-return

example - En C#, ¿por qué un método anónimo no puede contener una declaración de rendimiento?



summary example c# (4)

Pensé que sería bueno hacer algo como esto (con la lambda haciendo un 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 yield en método anónimo. Me pregunto por qué. Los productores solo dicen que no está permitido.

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


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

Sin embargo, los métodos anónimos ya son una pieza de "magia del compilador" en el sentido de que el método se extraerá a un método en la clase existente, o incluso a una clase completamente nueva, dependiendo de si se trata de variables locales o no.

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

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 en hacer 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 sugeriría que utilice el sitio de Microsoft Connect e informe una pregunta, estoy seguro de que obtendrá algo utilizable a cambio.


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

En particular, los bloques iteradores son implementados por algunas transformaciones de código de compilador sofisticadas. 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 prohibidos de la interacción.

La forma en que los bloques de iteradores funcionan bajo el capó se maneja bien here .

Como un simple ejemplo 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 simultáneamente quiere convertir esto a 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 del iterador está tratando de hacer su trabajo para hacer una pequeña máquina de estados. Ciertos ejemplos simples podrían funcionar con una buena cantidad de comprobación de cordura (primero, tratar con los cierres posiblemente relacionados de forma arbitraria) y luego ver si las clases resultantes del nivel inferior podrían 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 al menos 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 incluso existía una pequeña posibilidad de superposición cuando era imposible o suficientemente difícil no implementarla, la cantidad de problemas de soporte resultantes probablemente sería alta ya que el cambio de rotura sutil se perdería en muchos usuarios.
  3. Se puede solucionar fácilmente.

En tu 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; }


Eric Lippert recientemente escribió una serie de publicaciones en blogs sobre por qué no se permite el rendimiento en algunos casos.

EDIT2:

  • Parte 7 (esta fue publicada más tarde y específicamente aborda esta pregunta)

Probablemente encontrarás la respuesta allí ...

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

P:

Eric,

¿También puede proporcionar una idea de por qué "rendimientos" no están permitidos dentro de un método anónimo o expresión lambda

UN :

Buena pregunta. Me encantaría tener bloques de iteradores anónimos. Sería increíble poder construir un pequeño generador de secuencias en el lugar que cerrara las variables locales. La razón por la que no es sencillo: los beneficios no superan los costos. La genialidad de hacer que los generadores de secuencias en el lugar sean realmente pequeños en el gran esquema de las cosas y los métodos nominales hacen el trabajo lo suficientemente bien en la mayoría de los escenarios. Entonces los beneficios no son tan convincentes.

Los costos son grandes La reescritura de Iterator es la transformación más complicada en el compilador, y la reescritura de métodos anónimos es la segunda más complicada. Los métodos anónimos pueden estar dentro de otros métodos anónimos, y los métodos anónimos pueden estar dentro de los bloques de iteradores. Por lo tanto, lo que hacemos es primero reescribir todos los métodos anónimos para que se conviertan en métodos de una clase de cierre. Esta es la penúltima cosa que hace el compilador antes de emitir IL para un método. Una vez hecho ese paso, el reescritor de iteradores puede suponer que no hay métodos anónimos en el bloque de iteradores; todos han sido reescritos ya. Por lo tanto, el reescritor de iteradores puede concentrarse en reescribir el iterador, sin preocuparse de que pueda haber un método anónimo no realizado allí.

Además, los bloques de iteradores nunca "anidan", a diferencia de los métodos anónimos. El reescritor de iteradores puede suponer que todos los bloques de iteradores son de "nivel superior".

Si se permite que los métodos anónimos contengan bloques de iteradores, ambas suposiciones desaparecerán. Puede tener un bloque iterador que contenga un método anónimo que contenga un método anónimo que contenga un bloque iterador que contenga un método anónimo, y ... yuck. Ahora tenemos que escribir un pase de reescritura que pueda manejar bloques de iteradores anidados y métodos anónimos anidados al mismo tiempo, combinando nuestros dos algoritmos más complicados en un algoritmo mucho más complicado. Sería realmente difícil diseñar, implementar y probar. Somos lo suficientemente inteligentes como para hacerlo, estoy seguro. Tenemos un equipo inteligente aquí. Pero no queremos asumir esa gran carga de una característica de "bueno tener pero no necesaria". - Eric


Yo haría esto:

IList<T> list = GetList<T>(); var fun = expression.Compile(); return list.Where(item => fun.Invoke(item)).ToList();

Por supuesto, necesita el System.Core.dll al que se hace referencia desde .NET 3.5 para el método Linq. E incluye:

using System.Linq;

Aclamaciones,

Astuto