Czy istnieje sposób zadeklarowania C # lambda i natychmiastowe wywołanie?

29

Można zadeklarować funkcję lambda i natychmiast ją wywołać:

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

Zastanawiam się, czy można to zrobić w jednym wierszu, np. Coś takiego

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

co daje błąd kompilatora „Oczekiwana nazwa metody”. Przesyłanie do Func<int, int>nie działa:

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

daje ten sam błąd iz powodów wymienionych poniżej chciałbym uniknąć konieczności jawnego podawania typu argumentu wejściowego (pierwszy int).


Prawdopodobnie zastanawiasz się, dlaczego chcę to zrobić, zamiast bezpośrednio osadzać kod, np int output = 1;. Powód jest następujący: Wygenerowałem odwołanie do usługi internetowej SOAP svcutil, która ze względu na zagnieżdżone elementy generuje wyjątkowo długie nazwy klas, których nie chciałbym pisać. Więc zamiast

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

i osobna CreateAddress(GetOrderResultOrderShipment_OrderShipmentShipment_Address address)metoda (prawdziwe nazwy są jeszcze dłuższe, a ja mam bardzo ograniczoną kontrolę nad formularzem), chciałbym pisać

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

Wiem, że mógłbym pisać

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

ale nawet to ( sh.ReceiverAddress_Shipment.Addressczęść) staje się bardzo powtarzalne, jeśli istnieje wiele pól. Zadeklarowanie lambdy i natychmiastowe wywołanie byłoby bardziej eleganckim, mniejszym znakiem do pisania.

Glorfindel
źródło
int output = ((Func<int>) (() => { return 1; }))();
Dmitry Bychenko
Może po prostu napisz małe opakowanie public T Exec<T>(Func<T> func) => return func();i użyj go w ten sposób: int x = Exec(() => { return 1; });To dla mnie brzmi o wiele ładniej niż casting ze wszystkimi jego częściami.
germi
@Germi fajny pomysł, ale daje mi „Argumenty typu dla metody Exec nie można wywnioskować z użycia”.
Glorfindel
@Glorfindel Zrobiłeś coś złego, a następnie: dotnetfiddle.net/oku7eX
canton7
@ kanton7, bo faktycznie używam lambda z parametrem wejściowym ... Dzięki, teraz działa.
Glorfindel

Odpowiedzi:

29

Zamiast rzucać lambda, proponuję użyć funkcji małego pomocnika:

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

które można następnie wykorzystać tak: int x = Exec(myVar => myVar + 2, 0);. To brzmi o wiele ładniej niż sugerowane tutaj alternatywy.

germi
źródło
25

To brzydkie, ale możliwe:

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

Możesz rzucać, ale lambda musi być ujęta w nawiasy.

Powyższe można również uprościć:

int output = ((Func<int, int>)(input => 1))(0);
Johnathan Barclay
źródło
2
Ach, oczywiście. Próbowałem tylko, int output = (Func<int>)(() => { return 1; })();ale obsada ma niższy priorytet niż wykonanie lambda.
Glorfindel
Nadal nie rozwiązuje to problemu, że nie chce pisać wyjątkowo długich nazw klas.
Glorfindel
4

Literały lambda w języku C # mają dziwne rozróżnienie, ponieważ ich znaczenie zależy od ich typu. Są zasadniczo przeciążone rodzajem zwrotu, który nie istnieje nigdzie indziej w C #. (Literały liczbowe są nieco podobne.)

Dokładnie to samo lambda dosłowny może też ocenić do anonimowej funkcji, które można wykonać (czyli Func/ Action) lub abstrakcyjnej reprezentacji operacji wewnątrz korpusu, rodzaj jak Abstract Syntax drzewo (czyli LINQ Expression Tree).

To ostatnie dotyczy na przykład działania LINQ na SQL, LINQ na XML itp.: Lambda nie przetwarzają kodu wykonywalnego, lecz na drzewa wyrażeń LINQ, a dostawca LINQ może następnie użyć tych drzew wyrażeń, aby zrozumieć, co ciało lambda robi i generuje np. zapytanie SQL.

W twoim przypadku kompilator nie ma możliwości sprawdzenia, czy literał lambda ma być Funcwyrażony jako wyrażenie LINQ. Właśnie dlatego odpowiedź Johnathana Barclaya działa: nadaje typ wyrażeniu lambda, a zatem kompilator wie, że potrzebujesz Funcskompilowanego kodu, który wykonuje ciało twojej lambdy zamiast nieocenionego drzewa wyrażeń LINQ reprezentującego kod wewnątrz ciało lambda.

Jörg W Mittag
źródło
3

Możesz wstawić deklarację Funcwykonując

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

i natychmiast przywołując to.

phuzi
źródło
2

Możesz także utworzyć alias w Selectmetodzie

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

lub z ??operatorem

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
źródło
1

Jeśli nie masz nic przeciwko naruszeniu kilku wytycznych dotyczących projektowania metod rozszerzeń, metody rozszerzeń w połączeniu z operatorem null-warunkowym ?.mogą zabrać cię dość daleko:

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

da ci to:

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

a jeśli potrzebujesz głównie tablic, zastąp ToArraymetodę rozszerzającą, aby zawrzeć jeszcze kilka wywołań metod:

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

w wyniku czego:

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
źródło