Есть ли способ объявить C # лямбду и немедленно вызвать его?

29

Можно объявить лямбда-функцию и немедленно вызвать ее:

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

Мне интересно, если это возможно сделать в одну строку, например что-то вроде

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

что выдает ошибку компилятора "Ожидается имя метода". Приведение к Func<int, int>не работает:

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

выдает ту же ошибку, и по причинам, указанным ниже, я бы хотел избежать необходимости явно указывать тип входного аргумента (первый int).


Вы, наверное, задаетесь вопросом, почему я хочу сделать это, а не просто встраивать код, например int output = 1;. Причина в следующем: я сгенерировал ссылку на веб-сервис SOAP svcutil, который из-за вложенных элементов генерирует очень длинные имена классов, которые я хотел бы избежать при наборе. Так что вместо

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

и отдельный CreateAddress(GetOrderResultOrderShipment_OrderShipmentShipment_Address address)метод (реальные имена еще длиннее, и у меня очень ограниченный контроль над формой), я хотел бы написать

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

Я знаю, что мог бы написать

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

но даже это ( sh.ReceiverAddress_Shipment.Addressчасть) становится очень повторяющимся, если есть много полей. Объявление лямбды и немедленный вызов ее будет более изящным, чем писать меньше символов.

Глорфиндел
источник
int output = ((Func<int>) (() => { return 1; }))();
Дмитрий Быченко
Почему бы просто не написать маленькую обертку public T Exec<T>(Func<T> func) => return func();и использовать ее так: « int x = Exec(() => { return 1; });Мне кажется, это намного лучше, чем кастинг со всеми его паренами».
Джерми
@germi хорошая идея, но она дает мне «Аргументы типа для метода Exec не могут быть выведены из использования».
Глорфиндель
@Glorfindel Тогда вы сделали что-то не так: dotnetfiddle.net/oku7eX
canton7
@ canton7, потому что я на самом деле использую лямбду с входным параметром ... Спасибо, теперь это работает.
Глорфиндель

Ответы:

29

Вместо того, чтобы пытаться разыграть лямбду, я предлагаю вам использовать небольшую вспомогательную функцию:

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

которые вы могли бы использовать , как это: int x = Exec(myVar => myVar + 2, 0);. Это звучит намного лучше для меня, чем предлагаемые здесь альтернативы.

Germi
источник
25

Это некрасиво, но возможно

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

Вы можете разыграть, но лямбда должна быть заключена в скобки.

Вышесказанное также может быть упрощено:

int output = ((Func<int, int>)(input => 1))(0);
Джонатан Барклай
источник
2
Ах, конечно. Я только попробовал, int output = (Func<int>)(() => { return 1; })();но приведение имеет более низкий приоритет, чем лямбда-исполнение.
Глорфиндель
Это все еще не решает проблему нежелания писать чрезвычайно длинные имена классов.
Глорфиндель
4

Лямбда-литералы в C # имеют любопытное различие в том, что их значение зависит от их типа. Они по существу перегружены своим типом возврата, который в C # больше нигде не существует. (Числовые литералы несколько похожи.)

Точно такой же лямбда буквальным может либо вычисляться анонимной функции , которые можно выполнить (т.е. Func/ Action) или абстрактное представление операций внутри тела, вроде как абстрактное синтаксическое дерево (т.е. LINQ Expression Tree).

Последнее является, например, как LINQ к SQL, LINQ к XML и т.д. работы: лямбды не оценивают исполняемый код, они оценивают деревья выражений LINQ, и поставщик LINQ может затем использовать эти деревья выражений, чтобы понять, что тело лямбда делает и генерирует, например, SQL-запрос из этого.

В вашем случае у компилятора нет возможности узнать, должен ли лямбда-литерал быть оценен в Funcвыражении LINQ или. Вот почему ответ Джонатана Барклая работает: он задает тип лямбда-выражения, и поэтому компилятор знает, что вам нужен Funcкомпилированный код, который выполняет тело вашей лямбды, а не необработанное дерево выражений LINQ, представляющее код внутри тело лямбды.

Йорг Миттаг
источник
3

Вы могли бы встроить декларацию Func, выполнив

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

и немедленно вызывать его.

phuzi
источник
2

Вы также можете создать псевдоним в Selectметоде

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

или с ??оператором

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

Если вы не возражаете нарушить некоторые рекомендации по разработке методов расширения, методы расширения в сочетании с нулевым условным оператором ?.могут привести вас довольно далеко:

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

даст вам это:

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

и если вам в основном нужны массивы, переопределите ToArrayметод расширения, чтобы инкапсулировать еще несколько вызовов метода:

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

в результате чего:

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
        })
    })
};
Константин Спирин
источник