¿Hay alguna manera de declarar una C # lambda e inmediatamente llamarla?


29

Es posible declarar una función lambda e inmediatamente llamarla:

Func<int, int> lambda = (input) => { return 1; };
int output = lambda(0);

Me pregunto si es posible hacerlo en una línea, por ejemplo, algo como

int output = (input) => { return 1; }(0);

que da un error del compilador "Nombre del método esperado". Lanzar a Func<int, int>tampoco funciona:

int output = (Func<int, int>)((input) => { return 1; })(0);

da el mismo error y, por las razones mencionadas a continuación, me gustaría evitar tener que especificar explícitamente el tipo de argumento de entrada (el primero int).


Probablemente se esté preguntando por qué quiero hacer esto, en lugar de simplemente incrustar el código directamente, por ejemplo int output = 1;. La razón es la siguiente: he generado una referencia para un servicio web SOAP con svcutil, que debido a los elementos anidados genera nombres de clase extremadamente largos, que me gustaría evitar tener que escribir. Entonces en lugar de

var o = await client.GetOrderAsync(request);
return new Order {
    OrderDate = o.OrderDate,
    ...
    Shipments = o.Shipment_Order == null ? new Shipment[0]
        o.Shipment_Order.Select(sh => new Shipment {
            ShipmentID = sh.ShipmentID,
            ...
            Address = CreateAddress(sh.ReceiverAddress_Shipment);
        }).ToArray()
};

y un CreateAddress(GetOrderResultOrderShipment_OrderShipmentShipment_Address address)método separado (los nombres reales son aún más largos, y tengo un control muy limitado sobre el formulario), me gustaría escribir

var o = await client.GetOrderAsync(request);
return new Order {
    OrderDate = o.OrderDate,
    ...
    Shipments = o.Shipment_Order == null ? new Shipment[0]
        o.Shipment_Order.Select(sh => new Shipment {
            ShipmentID = sh.ShipmentID,
            ...
            Address = sh.ReceiverAddress_Shipment == null ? null : () => {
                var a = sh.ReceiverAddress_Shipment.Address;
                return new Address {
                    Street = a.Street
                    ...
                };
            }()
        }).ToArray()
};

Se que podria escribir

Address = sh.ReceiverAddress_Shipment == null ? null : new Address {
    Street = sh.ReceiverAddress_Shipment.Address.Street,
    ...
}

pero incluso eso (la sh.ReceiverAddress_Shipment.Addressparte) se vuelve muy repetitivo si hay muchos campos. Declarar una lambda e inmediatamente llamarla sería más elegante, menos caracteres para escribir.


int output = ((Func<int>) (() => { return 1; }))();
Dmitry Bychenko

¿Por qué no simplemente escribir un pequeño envoltorio? public T Exec<T>(Func<T> func) => return func();Y usarlo así: int x = Exec(() => { return 1; });eso para mí se lee mucho mejor que el casting con todos sus padres.
germi

Buena idea de @germi, pero me da "Los argumentos de tipo para el método Exec no se pueden inferir del uso".
Glorfindel

@Glorfindel Hiciste algo mal, entonces: dotnetfiddle.net/oku7eX
canton7

@ canton7 porque en realidad estoy usando una lambda con parámetro de entrada ... Gracias, funciona ahora.
Glorfindel

Respuestas:


29

En lugar de intentar lanzar el lambda, te propongo que uses una pequeña función auxiliar:

public static TOut Exec<TIn, TOut>(Func<TIn, TOut> func, TIn input) => func(input);

que luego se podría utilizar como esto: int x = Exec(myVar => myVar + 2, 0);. Esto me parece mucho mejor que las alternativas sugeridas aquí.


25

Es feo, pero es posible:

int output = ((Func<int, int>)(input => { return 1; }))(0);

Puede emitir, pero la lambda debe estar entre paréntesis.

Lo anterior también se puede simplificar:

int output = ((Func<int, int>)(input => 1))(0);

2
Ah, por supuesto. Solo lo intenté int output = (Func<int>)(() => { return 1; })();pero el elenco tiene menor prioridad que la ejecución lambda.
Glorfindel

Sin embargo, todavía no resuelve el problema de no querer escribir los nombres de clase extremadamente largos.
Glorfindel

4

Los literales lambda en C # tienen una curiosa distinción en que su significado depende de su tipo. Están esencialmente sobrecargados en su tipo de retorno, que es algo que no existe en ningún otro lugar en C #. (Los literales numéricos son algo similares).

La exacta misma literal lambda puede o bien evaluar a una función anónima que se puede ejecutar (es decir, un Func/ Action) o una representación abstracta de las operaciones en el interior del cuerpo, como una especie de árbol de sintaxis abstracta (es decir, una expresión LINQ árbol).

Lo último es, por ejemplo, cómo funcionan LINQ to SQL, LINQ to XML, etc.: las lambdas no evalúan el código ejecutable, evalúan los árboles de expresión LINQ, y el proveedor de LINQ puede usar esos árboles de expresión para comprender qué El cuerpo del lambda está haciendo y genera, por ejemplo, una consulta SQL a partir de eso.

En su caso, no hay forma de que el compilador sepa cuándo se supone que el literal lambda debe ser evaluado en una Funco una expresión LINQ. Es por eso que la respuesta de Johnathan Barclay funciona: le da un tipo a la expresión lambda y, por lo tanto, el compilador sabe que desea un Funccódigo compilado que ejecute el cuerpo de su lambda en lugar de un árbol de expresión LINQ no evaluado que representa el código dentro El cuerpo de la lambda.


3

Se podría inline la declaración de la Funchaciendo

int output = (new Func<int, int>(() => { return 1; }))(0);

e inmediatamente invocandolo.


2

También puedes crear el alias en el Selectmétodo

var o = await client.GetOrderAsync(request);
return new Order {
    OrderDate = o.OrderDate,
    ...
    Shipments = o.Shipment_Order == null ? new Shipment[0]
        o.Shipment_Order.Select(sh => {
          var s = sh.ReceiverAddress_Shipment;
          var a = s.Address;
          return new Shipment {
            ShipmentID = sh.ShipmentID,
            ...
            Address = s == null ? 
                      null : 
                      new Address {
                        Street = a.Street
                        ...
                      }
          };
        }).ToArray()
};

o con el ??operador

var o = await client.GetOrderAsync(request);
return new Order {
    OrderDate = o.OrderDate,
    ...
    Shipments = o.Shipment_Order?.Select(sh => {
        var s = sh.ReceiverAddress_Shipment;
        var a = s.Address;
        return new Shipment {
            ShipmentID = sh.ShipmentID,
            ...
            Address = s == null ? 
                      null : 
                      new Address {
                          Street = a.Street
                          ...
                      }
        };
    }).ToArray() ?? new Shipment[0]
};

1

Si no le importa violar algunas de las pautas de diseño de los métodos de extensión, los métodos de extensión combinados con el operador condicional nulo ?.pueden llevarlo razonablemente lejos:

public static class Extensions
{
    public static TOut Map<TIn, TOut>(this TIn value, Func<TIn, TOut> map)
        where TIn : class
        => value == null ? default(TOut) : map(value);

    public static IEnumerable<T> OrEmpty<T>(this IEnumerable<T> items)
        => items ?? Enumerable.Empty<T>();
}

te dará esto:

return new Order
{
    OrderDate = o.OrderDate,
    Shipments = o.Shipment_Order.OrEmpty().Select(sh => new Shipment
    {
        ShipmentID = sh.ShipmentID,
        Address = sh.ReceiverAddress_Shipment?.Address.Map(a => new Address
        {
            Street = a.Street
        })
    }).ToArray()
};

y si en su mayoría necesita matrices, anule el ToArraymétodo de extensión para encapsular algunas llamadas de método más:

public static TOut[] ToArray<TIn, TOut>(this IEnumerable<TIn> items, Func<TIn, TOut> map)
    => items == null ? new TOut[0] : items.Select(map).ToArray();

Resultando en:

return new Order
{
    OrderDate = o.OrderDate,
    Shipments = o.Shipment_Order.ToArray(sh => new Shipment
    {
        ShipmentID = sh.ShipmentID,
        Address = sh.ReceiverAddress_Shipment?.Address.Map(a => new Address
        {
            Street = a.Street
        })
    })
};
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.