Gibt es eine Möglichkeit, ein C # -Lambda zu deklarieren und sofort aufzurufen?

29

Es ist möglich, eine Lambda-Funktion zu deklarieren und sofort aufzurufen:

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

Ich frage mich, ob es möglich ist, dies in einer Zeile zu tun, z

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

Dies gibt einen Compilerfehler "Methodenname erwartet". Casting to Func<int, int>funktioniert auch nicht:

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

gibt den gleichen Fehler aus, und aus den unten genannten Gründen möchte ich vermeiden, den Eingabeargumenttyp (den ersten int) explizit angeben zu müssen .


Sie fragen sich wahrscheinlich, warum ich das tun möchte, anstatt nur den Code direkt einzubetten, z int output = 1;. Der Grund ist folgender: Ich habe eine Referenz für einen SOAP-Webservice mit generiert svcutil, der aufgrund der verschachtelten Elemente extrem lange Klassennamen generiert, die ich vermeiden möchte, eingeben zu müssen. Also statt

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

und eine separate CreateAddress(GetOrderResultOrderShipment_OrderShipmentShipment_Address address)Methode (echte Namen sind noch länger und ich habe nur sehr begrenzte Kontrolle über das Formular), möchte ich schreiben

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

Ich weiß, ich könnte schreiben

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

Aber selbst das (der sh.ReceiverAddress_Shipment.AddressTeil) wiederholt sich sehr, wenn es viele Felder gibt. Ein Lambda zu deklarieren und es sofort aufzurufen, wäre eleganter, weniger Zeichen zu schreiben.

Glorfindel
quelle
int output = ((Func<int>) (() => { return 1; }))();
Dmitry Bychenko
Warum nicht einfach einen kleinen Wrapper schreiben public T Exec<T>(Func<T> func) => return func();und ihn so verwenden: int x = Exec(() => { return 1; });Das liest sich für mich viel schöner als das Casting mit all seinen Parens.
Germi
@germi nette Idee, aber es gibt mir "Die Typargumente für die Methode Exec können nicht aus der Verwendung abgeleitet werden."
Glorfindel
@Glorfindel Du hast also etwas falsch gemacht: dotnetfiddle.net/oku7eX
canton7
@ canton7, weil ich tatsächlich ein Lambda mit Eingabeparameter verwende ... Danke, es funktioniert jetzt.
Glorfindel

Antworten:

29

Anstatt zu versuchen, das Lambda zu wirken, schlage ich vor, dass Sie eine kleine Hilfsfunktion verwenden:

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

die du dann so verwenden könntest : int x = Exec(myVar => myVar + 2, 0);. Das liest sich für mich viel besser als die hier vorgeschlagenen Alternativen.

Germi
quelle
25

Es ist hässlich, aber es ist möglich:

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

Sie können werfen, aber das Lambda muss in Klammern stehen.

Das Obige kann ebenfalls vereinfacht werden:

int output = ((Func<int, int>)(input => 1))(0);
Johnathan Barclay
quelle
2
Ah natürlich. Ich habe es nur versucht, int output = (Func<int>)(() => { return 1; })();aber die Besetzung hat eine niedrigere Priorität als die Lambda-Ausführung.
Glorfindel
Es löst jedoch immer noch nicht das Problem, die extrem langen Klassennamen nicht schreiben zu wollen.
Glorfindel
4

Lambda-Literale in C # unterscheiden sich merkwürdig darin, dass ihre Bedeutung von ihrem Typ abhängt. Sie sind bei ihrem Rückgabetyp im Wesentlichen überlastet, was in C # nirgendwo anders vorhanden ist. (Numerische Literale sind etwas ähnlich.)

Das exakt gleiche Lambda-Literal kann entweder eine anonyme Funktion auswerten, die Sie ausführen können (dh ein Func/ Action), oder eine abstrakte Darstellung der Operationen innerhalb des Körpers, ähnlich einem abstrakten Syntaxbaum (dh einem LINQ-Ausdrucksbaum).

Letzteres ist beispielsweise die Funktionsweise von LINQ zu SQL, LINQ zu XML usw.: Die Lambdas werden nicht zu ausführbarem Code ausgewertet, sie werden zu LINQ-Ausdrucksbäumen ausgewertet, und der LINQ-Anbieter kann diese Ausdrucksbäume dann verwenden, um zu verstehen, was die Körper des Lambda macht und generiert zB eine SQL-Abfrage daraus.

In Ihrem Fall kann der Compiler nicht wissen, ob das Lambda-Literal zu einem Funcoder einem LINQ-Ausdruck ausgewertet werden soll . Deshalb Johnathan Barclay Antwort Werke: es gibt einen Typ zum Ausdruck Lambda und damit der Compiler weiß , dass Sie ein Funcmit kompilierten Code, führt den Körper Ihrer Lambda statt einer nicht ausgewertet LINQ Expression Tree, repräsentiert den Code in der Körper des Lambda.

Jörg W Mittag
quelle
3

Sie könnten die Deklaration des inline Funceinfügen

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

und sofort aufrufen.

Phuzi
quelle
2

Sie können den Alias ​​auch in der SelectMethode erstellen

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

oder mit dem ??Betreiber

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
quelle
1

Wenn es Ihnen nichts ausmacht, gegen einige der Entwurfsrichtlinien für Erweiterungsmethoden zu verstoßen , können Erweiterungsmethoden in Kombination mit einem nullbedingten Operator ?.Sie einigermaßen weit bringen:

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

wird dir folgendes geben:

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

Wenn Sie hauptsächlich Arrays benötigen, überschreiben Sie die ToArrayErweiterungsmethode, um einige weitere Methodenaufrufe zu kapseln:

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

ergebend:

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
quelle