有没有办法声明C#lambda并立即调用它?


29

可以声明一个lambda函数并立即调用它:

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 Web服务的引用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如果有很多字段,那么即使是那部分也变得非常重复。声明一个lambda并立即调用它会更加优雅,减少编写字符。


int output = ((Func<int>) (() => { return 1; }))();
德米特里·拜琴科

为什么不只写一个小的包装器呢?public T Exec<T>(Func<T> func) => return func();并像这样使用它:int x = Exec(() => { return 1; });对我来说,这比带有所有括号的转换更好。
杰米

@germi是个好主意,但这给了我“无法从用法中推断出方法Exec的类型参数”。
Glorfindel

@Glorfindel您做错了,然后:dotnetfiddle.net/oku7eX
canton7

@ canton7,因为我实际上正在使用带有输入参数的lambda ...谢谢,它现在可以工作了。
Glorfindel

Answers:


29

我建议您使用一个小的辅助函数,而不是强制转换lambda:

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

您可以进行强制转换,但是lambda必须用括号括起来。

上面的内容也可以简化:

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

2
嗯当然了 我只是尝试过,int output = (Func<int>)(() => { return 1; })();但强制转换的优先级比lambda执行的优先级低。
Glorfindel

但是,它仍然不能解决不想编写极长的类名的问题。
Glorfindel

4

C#中的Lambda文字有一个奇怪的区别,因为它们的含义取决于它们的类型。它们本质在其返回类型上重载的这是C#中其他地方所不存在的。(数字文字有点相似。)

完全相同的拉姆达的文字可以任意计算为匿名函数,你可以执行(即Func/ Action操作的机构内部的抽象表示,有点像一个抽象语法树(即LINQ表达式树)。

后者例如是LINQ to SQL,LINQ to XML等的工作方式:lambda 评估为可执行代码,它们评估为LINQ Expression Trees,然后LINQ提供者可以使用这些Expression Tree来理解什么。 lambda主体正在执行并从中生成例如SQL查询。

在您的情况下,编译器无法知道应将lambda文字评估为Func还是LINQ表达式。这就是Johnathan Barclay的答案起作用的原因:它为lambda表达式提供了一种类型,因此,编译器知道您想要一个Func带有已编译代码的代码来执行 lambda的主体,而不是一个未评估的LINQ Expression Tree来表示内部代码lambda的主体。


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
        })
    })
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.