有没有一种方法可以声明一个C# lambda表达式并立即调用它?

30

可以声明一个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;。原因如下:我使用 svcutil 生成了一个SOAP webservice的参考,由于嵌套元素的存在,它生成了非常长的类名,我希望避免手动敲入这些名称。所以,我想通过重载方法来实现更短的调用方式。

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; }))(); - Dmitry Bychenko
为什么不写一个小包装器:public T Exec<T>(Func<T> func) => return func();,然后像这样使用它:int x = Exec(() => { return 1; }); 对我来说,这比所有括号的强制转换更易读。 - germi
@Glorfindel,你做错了什么:https://dotnetfiddle.net/oku7eX - canton7
最终我采用了你的包装器解决方案,确切地说是 public U Exec<T, U>(Func<T, U> func, T input) => func(input);。你可能想将其扩展为一个答案。 - Glorfindel
一个更简单的替代方案可能是使用type aliases。它们支持命名空间和类。 - Voo
显示剩余3条评论
6个回答

30

不要试图转换lambda表达式,我建议您使用一个小的辅助函数:

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

你可以像这样使用它:int x = Exec(myVar => myVar + 2, 0);。对我来说,这读起来比这里提出的其他选择都要好。


27

虽然不美观,但是它是可能的:

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

匿名函数,包括lambda表达式,可以隐式转换为与它们签名匹配的委托,但这种语法要求将lambda表达式括在括号中。

上述内容也可以简化:

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

2
当然,我只尝试了 int output = (Func<int>)(() => { return 1; })(); 但是强制转换的优先级低于 lambda 执行。 - Glorfindel
1
然而,这仍然没有解决不想编写极长类名的问题。 - Glorfindel

5
C#中的Lambda字面量有一个奇怪的区别,即它们的含义取决于它们的类型。它们本质上是根据其返回类型进行重载的,这在C#的任何其他地方都不存在。(数字字面量有些类似。)
相同的lambda字面量可以评估为您可以执行的匿名函数(即Func / Action),也可以评估为Body内部操作的抽象表示,有点像抽象语法树(即LINQ表达式树)。
例如,LINQ to SQL,LINQ to XML等的工作方式就是后者:lambda不会评估为可执行代码,而是评估为LINQ表达式树,然后LINQ提供程序可以使用这些表达式树来了解lambda主体正在做什么,并从中生成SQL查询。
在您的情况下,编译器无法知道 lambda 字面值应该被评估为一个 Func 还是 LINQ 表达式。这就是为什么 Johnathan Barclay 的答案 能够起作用的原因:它为 lambda 表达式提供了一种类型,因此编译器知道您想要一个带有已编译代码的 Func,该代码 执行 您的 lambda 主体,而不是一个未评估的 LINQ 表达式树,该表达式树 表示 lambda 主体中的代码。

如果 int output = ((int input) => 1)(0) 能够正常工作就太好了。编译器所需的所有类型信息都已经提供了。这对于在标量变量上执行类似于 LINQ 的 .Select() 操作非常方便。 - undefined

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

will give you this:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接