C'è un modo per dichiarare un lambda C # e chiamarlo immediatamente?


29

È possibile dichiarare una funzione lambda e chiamarla immediatamente:

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

Mi chiedo se sia possibile farlo in una riga, ad esempio qualcosa del genere

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

che fornisce un errore del compilatore "Nome metodo previsto". Casting Func<int, int>non funziona neanche:

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

fornisce lo stesso errore e per i motivi indicati di seguito vorrei evitare di specificare esplicitamente il tipo di argomento di input (il primo int).


Probabilmente ti starai chiedendo perché voglio farlo, invece di incorporare direttamente il codice, ad es int output = 1;. Il motivo è il seguente: ho generato un riferimento per un servizio web SOAP con svcutil, che a causa degli elementi nidificati genera nomi di classe estremamente lunghi, che vorrei evitare di dover digitare. Quindi invece di

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 un CreateAddress(GetOrderResultOrderShipment_OrderShipmentShipment_Address address)metodo separato (i nomi reali sono ancora più lunghi e ho un controllo molto limitato sul modulo), mi piacerebbe scrivere

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

So di poter scrivere

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

ma anche quello (la sh.ReceiverAddress_Shipment.Addressparte) diventa molto ripetitivo se ci sono molti campi. Dichiarare un lambda e chiamarlo immediatamente sarebbe più elegante meno personaggi da scrivere.


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

Perché non scrivere un piccolo involucro: public T Exec<T>(Func<T> func) => return func();e usarlo in questo modo: int x = Exec(() => { return 1; });per me è molto più bello del casting con tutte le sue parentesi.
germi

@germi bella idea, ma mi dà "Gli argomenti di tipo per il metodo Exec non possono essere dedotti dall'uso."
Glorfindel,

@Glorfindel Hai fatto qualcosa di sbagliato, quindi: dotnetfiddle.net/oku7eX
canton7

@ canton7 perché in realtà sto usando un lambda con parametro di input ... Grazie, ora funziona.
Glorfindel,

Risposte:


29

Invece di provare a lanciare la lambda, ti propongo di utilizzare una piccola funzione di supporto:

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

che si potrebbe poi utilizzare in questo modo: int x = Exec(myVar => myVar + 2, 0);. Questo mi sembra molto più bello delle alternative suggerite qui.


25

È brutto, ma è possibile:

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

Puoi lanciare, ma la lambda deve essere racchiusa tra parentesi.

Anche quanto sopra può essere semplificato:

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

2
Ah certo. Ho solo provato, int output = (Func<int>)(() => { return 1; })();ma il cast ha una priorità inferiore rispetto all'esecuzione lambda.
Glorfindel,

Tuttavia non risolve ancora il problema di non voler scrivere nomi di classe estremamente lunghi.
Glorfindel,

4

I letterali lambda in C # hanno una curiosa distinzione in quanto il loro significato dipende dal loro tipo. Sono essenzialmente sovraccarichi sul loro tipo di ritorno che è qualcosa che non esiste in nessun altro posto in C #. (I letterali numerici sono in qualche modo simili.)

Lo stesso identico lambda letterale può o restituire una funzione anonima che è possibile eseguire (cioè un Func/ Action) o di una rappresentazione astratta delle operazioni all'interno del Corpo, un po 'come un albero di sintassi astratta (vale a dire un LINQ Expression Tree).

Quest'ultimo è, ad esempio, come funziona LINQ to SQL, LINQ to XML, ecc.: I lambda non valutano il codice eseguibile, valutano gli alberi delle espressioni LINQ e il provider LINQ può quindi utilizzare quegli alberi delle espressioni per capire quali body of the lambda sta facendo e genera ad esempio una query SQL.

Nel tuo caso, non c'è modo per il compilatore di sapere se si suppone che il valore letterale lambda sia valutato in Funcun'espressione LINQ. Ecco perché la risposta di Johnathan Barclay funziona: dà un tipo all'espressione lambda e quindi, il compilatore sa che vuoi un Funccodice compilato che esegua il corpo del tuo lambda invece di un albero di espressioni LINQ non valutato che rappresenti il codice all'interno il corpo della lambda.


3

È possibile includere la dichiarazione del Funcfacendo

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

e immediatamente invocandolo.


2

Puoi anche creare l'alias nel Selectmetodo

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 l' ??operatore

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

Se non ti dispiace violare alcune delle linee guida di progettazione dei metodi di estensione, i metodi di estensione combinati con l'operatore null-condizionale ?.possono portarti ragionevolmente lontano:

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

ti darò questo:

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 hai principalmente bisogno di matrici, sostituisci il ToArraymetodo di estensione per incapsulare qualche altra chiamata di metodo:

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

con il risultato di:

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
        })
    })
};
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.