Existe uma maneira de declarar um C # lambda e chamá-lo imediatamente?

29

É possível declarar uma função lambda e chamá-la imediatamente:

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

Gostaria de saber se é possível fazê-lo em uma linha, por exemplo, algo como

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

que fornece um erro do compilador "Nome do método esperado". A transmissão para Func<int, int>também não funciona:

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

dá o mesmo erro e, pelos motivos mencionados abaixo, gostaria de evitar precisar especificar explicitamente o tipo de argumento de entrada (o primeiro int).


Você provavelmente está se perguntando por que eu quero fazer isso, em vez de apenas incorporar o código diretamente, por exemplo int output = 1;. O motivo é o seguinte: Gerei uma referência para um serviço da web SOAP com o svcutilqual, por causa dos elementos aninhados, gera nomes de classe extremamente longos, que eu gostaria de evitar de digitar. Então, ao invés 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()
};

e um CreateAddress(GetOrderResultOrderShipment_OrderShipmentShipment_Address address)método separado (nomes reais são ainda maiores e eu tenho um controle muito limitado sobre o formulário), gostaria de escrever

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

Eu sei que eu poderia escrever

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

mas mesmo isso (a sh.ReceiverAddress_Shipment.Addressparte) se torna muito repetitivo se houver muitos campos. Declarar um lambda e chamá-lo imediatamente seria mais elegante, menos caracteres para escrever.

Glorfindel
fonte
int output = ((Func<int>) (() => { return 1; }))();
Dmitry Bychenko 17/01
Por que não escrever um pequeno invólucro: public T Exec<T>(Func<T> func) => return func();e usá-lo assim: int x = Exec(() => { return 1; });isso para mim é muito melhor do que o elenco com todas as suas características.
germi 17/01
@germi boa idéia, mas me dá "Os argumentos de tipo para o método Exec não podem ser inferidos a partir do uso".
Glorfindel
@Glorfindel Você fez algo errado, então: dotnetfiddle.net/oku7eX
canton7 17/01
@ canton7 porque na verdade estou usando um lambda com parâmetro de entrada ... Obrigado, ele funciona agora.
Glorfindel

Respostas:

29

Em vez de tentar converter o lambda, proponho que você use uma pequena função auxiliar:

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

que você pode então usar assim: int x = Exec(myVar => myVar + 2, 0);. Isso me parece muito melhor do que as alternativas sugeridas aqui.

germi
fonte
25

É feio, mas é possível:

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

Você pode transmitir, mas o lambda precisa estar entre parênteses.

O acima pode ser simplificado também:

int output = ((Func<int, int>)(input => 1))(0);
Johnathan Barclay
fonte
2
Ah, claro. Eu só tentei, int output = (Func<int>)(() => { return 1; })();mas o elenco tem prioridade mais baixa que a execução lambda.
Glorfindel 17/01
Ainda não resolve o problema de não querer escrever os nomes de classe extremamente longos.
Glorfindel
4

Literais lambda em C # têm uma curiosa distinção, pois seu significado depende de seu tipo. Eles são essencialmente sobrecarregados em seu tipo de retorno que é algo que não existe em nenhum outro lugar no C #. (Literais numéricos são um pouco semelhantes.)

O mesmo literal lambda literal pode ser avaliado para uma função anônima que você pode executar (ou seja, um Func/ Action) ou uma representação abstrata das operações dentro do Body, como uma Árvore de Sintaxe Abstrata (por exemplo, uma Árvore de Expressão LINQ).

O último é, por exemplo, como o LINQ to SQL, o LINQ to XML etc. funciona: as lambdas não avaliam o código executável, avaliam as Árvores de Expressão do LINQ e o provedor LINQ pode então usá-las para entender o que os corpo do lambda está fazendo e gera, por exemplo, uma consulta SQL a partir disso.

No seu caso, não há como o compilador saber se o literal lambda deve ser avaliado como uma FuncExpressão ou LINQ. É por isso que a resposta de Johnathan Barclay funciona: ela fornece um tipo para a expressão lambda e, portanto, o compilador sabe que você deseja um Funccódigo compilado que execute o corpo da sua lambda em vez de uma Árvore de Expressão LINQ não avaliada que representa o código dentro o corpo da lambda.

Jörg W Mittag
fonte
3

Você pode embutir a declaração do Funcfazendo

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

e invocando-o imediatamente.

phuzi
fonte
2

Você também pode criar o alias no 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()
};

ou com o ??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]
};
Cyril Durand
fonte
1

Se você não se importa de violar algumas das diretrizes de design dos métodos de extensão, os métodos de extensão combinados com o operador nulo-condicional ?.podem levar você razoavelmente longe:

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

lhe dará o seguinte:

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

e se você precisar de matrizes, substitua o ToArraymétodo de extensão para encapsular mais algumas chamadas de método:

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

resultando em:

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
        })
    })
};
Konstantin Spirin
fonte