Pregunta

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 el rendimiento en un método anónimo. Me pregunto por qué. Los documentos de rendimiento solo dicen que no está permitido.

Como no estaba permitido, acabo de crear Lista y le agregué los elementos.

¿Fue útil?

Solución

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

EDIT2:

  • Parte 7 (esta se publicó más tarde y aborda específicamente 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:

Q:

  

Eric,

     

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

A:

  

Buena pregunta. Me encantaría tener   bloques iteradores anónimos. Podría ser   totalmente increíble poder construir   usted mismo un pequeño generador de secuencia   en el lugar que cerró sobre locales   variables La razón por la que no es   sencillo: los beneficios no   superan los costos. La genialidad de   haciendo generadores de secuencia en el lugar es   en realidad bastante pequeño en el gran   esquema de cosas y métodos nominales   hacer el trabajo lo suficientemente bien en la mayoría   escenarios Entonces los beneficios no son   que convincente.

     

Los costos son grandes. Iterador   reescribir es lo más complicado   transformación en el compilador, y   reescritura de método anónimo es el   El segundo más complicado. Anónimo   los métodos pueden estar dentro de otros anónimos   métodos y métodos anónimos pueden ser   dentro de bloques iteradores. Por lo tanto,   lo que hacemos es reescribir todo   métodos anónimos para que se conviertan   métodos de una clase de cierre. Esto es   lo penúltimo del compilador   hace antes de emitir IL para un método.   Una vez que se realiza ese paso, el iterador   reescritor puede asumir que no hay   métodos anónimos en el iterador   bloquear; todos han sido reescritos   ya. Por lo tanto el iterador   reescritor puede concentrarse en   reescribiendo el iterador, sin   preocupado de que pueda haber una   método anónimo no realizado allí.

     

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

     

Si se permiten métodos anónimos   contienen bloques iteradores, entonces ambos   esos supuestos salen por la ventana.   Puede tener un bloque iterador que   contiene un método anónimo que   contiene un método anónimo que   contiene un bloque iterador que   contiene un método anónimo y ...   qué asco. Ahora tenemos que escribir una reescritura   pase que puede manejar iterador anidado   bloques y métodos anónimos anidados en   Al mismo tiempo, fusionando nuestros dos más   algoritmos complicados en un lejano   Algoritmo más complicado. Sería   ser realmente difícil de diseñar, implementar,   y prueba. Somos lo suficientemente inteligentes como para hacer   Entonces, estoy seguro. Tenemos un equipo inteligente   aquí. Pero no queremos enfrentarnos   esa gran carga para a & quo

Otros consejos

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 bloques iteradores

En particular, los bloques iteradores se implementan mediante algunas sofisticadas transformaciones de código del compilador. Estas transformaciones impactarían con las transformaciones que suceden dentro de funciones anónimas o lambdas de tal manera que, en ciertas circunstancias, ambos intentarían 'convertir' el código en alguna otra construcción que fuera incompatible con la otra.

Como resultado, se les prohíbe la interacción.

El funcionamiento de los bloques iteradores bajo el capó se trata bien aquí .

Como un simple ejemplo de 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 desea simultáneamente convertir esto en algo 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 intenta hacer su trabajo para hacer una pequeña máquina de estados. Ciertos ejemplos simples podrían funcionar con una buena cantidad de verificación de cordura (primero tratando con los cierres anidados (posiblemente arbitrariamente)) y luego viendo si las clases resultantes de nivel inferior podrían transformarse en máquinas de estado iterador.

Sin embargo, esto sería

  1. Mucho trabajo.
  2. No podría funcionar en todos los casos sin, al menos, 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 hubiera incluso una pequeña posibilidad de superposición donde era imposible o lo suficientemente difícil de no implementar, entonces el número de problemas de soporte resultantes probablemente sería alto ya que el cambio sutil se perdería en muchos usuarios.
  3. Se puede solucionar muy fácilmente.

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

Desafortunadamente no sé por qué no permitieron esto, 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 "compilador mágico" 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 trata con variables locales o no.

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

Supongo que uno de estos dos hace que el código no sea identificable para 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 opción consciente, y que simplemente no funciona porque nadie pensó en implementarla.

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

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 referenciado desde .NET 3.5 para el método Linq. E incluye:

using System.Linq;

Saludos,

Sly

Quizás sea solo una limitación de sintaxis. En Visual Basic .NET, que es muy similar a C #, es perfectamente posible mientras que es incómodo escribir

Sub Main()
    Console.Write("x: ")
    Dim x = CInt(Console.ReadLine())
    For Each elem In Iterator Function()
                         Dim i = x
                         Do
                             Yield i
                             i += 1
                             x -= 1
                         Loop Until i = x + 20
                     End Function()
        Console.WriteLine(
static void Main()
{
    Console.Write("x: ");
    var x = System.Convert.ToInt32(Console.ReadLine());
    // ERROR: CS0149 - Method name expected 
    foreach (var elem in () =>
    {
        var i = x;
        do
        {
            yield return i;
            i += 1;
            x -= 1;
        }
        while (!i == x + 20);
    }())
        Console.WriteLine(<*>quot;{elem} to {x}");
    Console.ReadKey();
}
quot;{elem} to {x}") Next Console.ReadKey() End Sub

Observe también los paréntesis 'aquí ; la función lambda Función Iterator ... End Function devuelve un IEnumerable (Of Integer) pero es no tal objeto en sí. Se debe llamar para obtener ese objeto.

El código convertido por [1] genera errores en C # 7.3 (CS0149):

<*>

Estoy totalmente en desacuerdo con la razón dada en las otras respuestas que es difícil de manejar para el compilador. La Iterator Function () que ve en el ejemplo de VB.NET se creó específicamente para los iteradores lambda.

En VB, existe la palabra clave Iterator ; no tiene contraparte de C #. En mi humilde opinión, no hay una razón real por la que esto no sea una característica de C #.

Entonces, si realmente desea funciones de iterador anónimo, actualmente use Visual Basic o (no lo he verificado) F #, como se indica en un comentario de Parte # 7 en la respuesta de @Thomas Levesque (do Ctrl + F para F #).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top