Есть ли способ объявить C# лямбду и немедленно вызвать его? - PullRequest
29 голосов
/ 17 января 2020

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

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) становится очень повторяющимся, если есть много полей. Объявление лямбды и немедленный вызов будет на 1030 * более элегантным * на 1031 * меньше символов для записи.

Ответы [ 6 ]

29 голосов
/ 17 января 2020

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

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

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

25 голосов
/ 17 января 2020

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

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

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

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

int output = ((Func<int, int>)(input => 1))(0);
4 голосов
/ 18 января 2020

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

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

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

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

3 голосов
/ 17 января 2020

Вы можете встроить объявление Func, выполнив

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

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

2 голосов
/ 17 января 2020

Вы также можете создать псевдоним в методе 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 голос
/ 22 января 2020

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

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
        })
    })
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...