Чи є спосіб оголосити лямбда 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);. Це читає мені набагато приємніше, ніж запропоновані тут альтернативи.


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 Query з цього.

У вашому випадку компілятор не може дізнатись, чи слід вважати лямбда-літерал, який має бути Funcвиражений у вираз LINQ. Ось чому відповідь Джонатана Барклая працює: він надає тип лямбда-виразу, і тому компілятор знає, що вам потрібно Funcскомпільований код, який виконує тіло вашої лямбда замість не оціненого дерева вираження LINQ, який представляє код всередині тіло лямбда.


3

Ви можете вписати декларацію про це Func, виконуючи

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

і негайно викликати його.


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
        })
    })
};
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.