为什么不能将匿名方法分配给var?


139

我有以下代码:

Func<string, bool> comparer = delegate(string value) {
    return value != "0";
};

但是,以下内容无法编译:

var comparer = delegate(string value) {
    return value != "0";
};

为何编译器无法找出它是Func<string, bool>?它采用一个字符串参数,并返回一个布尔值。相反,它给了我错误:

无法将匿名方法分配给隐式类型的局部变量。

我有一个猜测,那就是如果var版本已编译,如果我具有以下内容,它将缺乏一致性:

var comparer = delegate(string arg1, string arg2, string arg3, string arg4, string arg5) {
    return false;
};

由于Func <>最多只允许使用4个参数(在.NET 3.5中,这是我正在使用的参数),因此上述方法没有意义。也许有人可以澄清这个问题。谢谢。


3
请注意,在.NET 4中,有关4个参数的参数Func<>最多接受16个参数。
Anthony Pegram

感谢您的澄清。我正在使用.NET 3.5。
马龙

9
为什么会让编译器认为是Func<string, bool>Converter<string, bool>在我看来,这很像!
Ben Voigt


3
有时我想念VB ..Dim comparer = Function(value$) value <> "0"
Slai 2016年

Answers:


155

其他人已经指出,您可能意味着无限多种可能的委托类型。有什么特别之处,Func以至于应将其作为默认值而不是Predicateor Action或任何其他可能性?而且,对于lambda而言,为什么明显是要选择委托形式而不是表达式树形式呢?

但是我们可以说这Func很特殊,并且lambda或匿名方法的推断类型是某物的Func。我们仍然会遇到各种各样的问题。在以下情况下,您想推断出什么类型?

var x1 = (ref int y)=>123;

没有Func<T>类型可以引用任何东西。

var x2 = y=>123;

尽管我们知道返回值,但我们不知道形式参数的类型。(还是我们?return int?long?short?byte?)

var x3 = (int y)=>null;

我们不知道返回类型,但是它不能为空。返回类型可以是任何引用类型或任何可为空的值类型。

var x4 = (int y)=>{ throw new Exception(); }

同样,我们不知道返回类型,这一次它可能是无效的。

var x5 = (int y)=> q += y;

它是要作为返回void的语句lambda还是返回分配给q的值的东西?两者都是合法的;我们应该选择哪个?

现在,您可能会说,嗯,就是不支持任何这些功能。仅支持可以计算出类型的“正常”情况。那没有帮助。这如何使我的生活更轻松?如果该功能有时有效并且有时会失败,那么我仍然必须编写代码以检测所有这些失败情况并为每种情况提供有意义的错误消息。我们仍然必须指定所有行为,将其记录下来,为它编写测试,等等。这是一项非常昂贵的功能,可以为用户节省六次击键。与花大量时间编写测试用例相比,我们有一种更好的方法可以为语言增添价值,因为该功能不能在一半时间内起作用,并且在起作用的情况下几乎没有任何好处。

实际上有用的情况是:

var xAnon = (int y)=>new { Y = y };

因为那东西没有“可口”的类型。但是我们一直都有这个问题,我们只是使用方法类型推断来推断类型:

Func<A, R> WorkItOut<A, R>(Func<A, R> f) { return f; }
...
var xAnon = WorkItOut((int y)=>new { Y = y });

现在,方法类型推断可以算出func类型是什么。


43
您什么时候将SO答案汇编成书?我会买:)
马特·格里尔

13
我将关于埃里克·利珀特(Eric Lippert)的SO答案书的建议第二。建议的标题:“从堆栈中反射”
Adam Rackis 2011年

24
@Eric:很好的答案,但是将其说明为不可能的事情会有点误导,因为这实际上在D中完全可以正常工作。只是你们没有选择为委托文字提供自己的类型,而是让他们依赖在他们的背景下...因此恕我直言,答案应该是“因为这就是我们做到的方式”,而不是其他任何事情。:)
user541686

5
@abstractdissonance我也注意到编译器是开源的。如果您关心此功能,则可以花费必要的时间和精力来实现它。我鼓励您提交请求请求。
埃里克·利珀特

7
@AbstractDissonance:我们根据有限的资源(开发人员和时间)来衡量成本。这个责任不是上帝所赋予的。它是由开发者部门的副总裁强加的。C#团队可能以某种方式忽略预算流程的想法很奇怪。我向您保证,权衡是通过C#社区表达了意愿的专家,Microsoft的战略使命以及他们自己在设计中的卓越品味的专家的审慎周到的考虑而做出的。
埃里克·利珀特

29

只有Eric Lippert可以肯定地知道,但是我认为这是因为委托类型的签名不能唯一地确定类型。

考虑您的示例:

var comparer = delegate(string value) { return value != "0"; };

这是关于var应该是什么的两个可能的推论:

Predicate<string> comparer  = delegate(string value) { return value != "0"; };  // okay
Func<string, bool> comparer = delegate(string value) { return value != "0"; };  // also okay

编译器应该推断哪个?没有充分的理由选择一个。尽管a Predicate<T>在功能上等同于a Func<T, bool>,但它们在.NET类型系统级别仍是不同的类型。因此,编译器无法明确解析委托类型,并且必须使类型推断失败。


1
我相信Microsoft的其他许多人也肯定知道。;)但是,是的,您暗示一个主要原因,由于没有编译时间类型,因此无法确定编译时间类型。语言规范的8.5.1节特别强调了禁止在隐式类型变量声明中使用匿名函数的原因。
Anthony Pegram

3
是的 更糟糕的是,对于lambda,我们甚至都不知道它是否将成为委托类型。它可能是一个表达式树。
埃里克·利珀特

任何有兴趣,我写了一点关于这一点,如何在C#和F#在接近对比mindscapehq.com/blog/index.php/2011/02/23/...
itowlson

为什么编译器不能为其lambda函数
构造

它们在“ .NET类型系统级别”有何区别?
arao6 '19

6

埃里克·利珀特(Eric Lippert)关于它的一篇旧文章

实际上,C#2.0规范对此进行了说明。方法组表达式和匿名方法表达式在C#2.0中是无类型的表达式,而lambda表达式在C#3.0中将它们连接在一起。因此,在隐式声明的右侧将它们“裸露”是非法的。


语言规范的第8.5.1节强调了这一点。“初始化程序表达式必须具有编译时类型”,以便用于隐式类型的局部变量。
安东尼·佩格拉姆

5

不同的代表被认为是不同的类型。例如,Action与有所不同MethodInvoker,并且的实例Action不能分配给type的变量MethodInvoker

因此,给定一个匿名委托(或lambda),例如() => {},它是an Action还是a MethodInvoker?编译器无法分辨。

同样,如果我声明一个接受string参数的委托类型并返回a bool,那么编译器将如何知道您真的想要a Func<string, bool>而不是我的委托类型?它无法推断委托类型。


2

以下几点来自MSDN,涉及隐式类型的局部变量:

  1. var仅可在同一语句中声明和初始化局部变量时使用;该变量不能初始化为null,方法组或匿名函数。
  2. var关键字指示编译器从初始化语句右侧的表达式中推断变量的类型。
  3. 重要的是要了解var关键字并不意味着“ variant”,也不表示变量是松散类型的或后期绑定的。这仅表示编译器确定并分配最合适的类型。

MSDN参考:隐式键入局部变量

考虑以下有关匿名方法的问题:

  1. 匿名方法使您可以省略参数列表。

MSDN参考:匿名方法

我会怀疑,由于匿名方法实际上可能具有不同的方法签名,因此编译器无法正确推断要分配的最合适的类型。


1

我的帖子没有回答实际问题,但确实回答了以下潜在问题:

“如何避免输入像这样的难看类型Func<string, string, int, CustomInputType, bool, ReturnType>?” [1]

作为我的懒惰/ hacky程序员,我尝试使用Func<dynamic, object>-接受单个输入参数并返回一个对象。

对于多个参数,可以这样使用它:

dynamic myParams = new ExpandoObject();
myParams.arg0 = "whatever";
myParams.arg1 = 3;
Func<dynamic, object> y = (dynObj) =>
{
    return dynObj.arg0.ToUpper() + (dynObj.arg1 * 45); //screw type casting, amirite?
};
Console.WriteLine(y(myParams));

提示:Action<dynamic>如果不需要返回对象,则可以使用。

是的,我知道这可能与您的编程原则背道而驰,但这对我和某些Python编码人员来说都是有意义的。

我是代表的新手,只是想分享我学到的东西。


[1]假定您没有调用需要预定义的方法Func作为参数的方法,在这种情况下,您将必须键入该难看的字符串:/


0

那怎么样

var item = new
    {
        toolisn = 100,
        LangId = "ENG",
        toolPath = (Func<int, string, string>) delegate(int toolisn, string LangId)
        {
              var path = "/Content/Tool_" + toolisn + "_" + LangId + "/story.html";
              return File.Exists(Server.MapPath(path)) ? "<a style=\"vertical-align:super\" href=\"" + path + "\" target=\"_blank\">execute example</a> " : "";
        }
};

string result = item.toolPath(item.toolisn, item.LangId);
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.