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

Posted

技术标签:

【中文标题】为啥不能将匿名方法分配给 var?【英文标题】:Why can't an anonymous method be assigned to var?为什么不能将匿名方法分配给 var? 【发布时间】:2011-06-25 08:00:45 【问题描述】:

我有以下代码:

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

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

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

为什么编译器不能确定它是Func&lt;string, bool&gt;?它接受一个字符串参数,并返回一个布尔值。相反,它给了我错误:

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

我有一个猜测,那就是如果编译了 var 版本,如果我有以下情况,它将缺乏一致性:

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

以上内容没有意义,因为 Func 最多只允许 4 个参数(在 .NET 3.5 中,这是我正在使用的)。也许有人可以澄清这个问题。谢谢。

【问题讨论】:

注意您的 4 个参数 参数,在 .NET 4 中,Func&lt;&gt; 最多接受 16 个参数。 感谢您的澄清。我正在使用 .NET 3.5。 为什么会让编译器认为这是Func&lt;string, bool&gt;?对我来说,它看起来像 Converter&lt;string, bool&gt; Why can't c# use inline anonymous lambdas or delegates? 的可能重复项 有时我会想念VB .. Dim comparer = Function(value$) value &lt;&gt; "0" 【参考方案1】:

其他人已经指出,您可以表示的委托类型有无限多; Func 有什么特别之处以至于它应该成为默认值而不是 PredicateAction 或任何其他可能性?而且,对于 lambdas,为什么它的意图是选择委托形式,而不是表达式树形式?

但是我们可以说Func 是特殊的,并且推断的 lambda 或匿名方法的类型是某种东西的 Func。我们仍然会遇到各种各样的问题。对于以下情况,您希望推断出哪些类型?

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

没有Func&lt;T&gt; 类型可以引用任何东西。

var x2 = y=>123;

我们不知道形参的类型,尽管我们知道返回值。 (或者我们呢?返回的是 int?long?short?byte?)

var x3 = (int y)=>null;

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

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

同样,我们不知道返回类型,这次它可以为 void。

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 类型是什么。

【讨论】:

你打算什么时候把你的 SO 答案编成一本书?我会买它:) 我支持 Eric Lippert 的 SO 答案书的提议。建议标题:“堆栈中的反射” @Eric:很好的答案,但是将其说明为不可能的事情有点误导,因为这实际上在 D 中完全可以正常工作。只是你们没有t 选择给委托文字他们自己的类型,而是让他们依赖于他们的上下文......所以恕我直言,答案应该是“因为这就是我们制作它的方式”,而不是其他任何事情。 :) @abstractdissonance 我还注意到编译器是开源的。如果您关心此功能,那么您可以付出必要的时间和精力来实现它。我鼓励你提交一个拉取请求。 @AbstractDissonance:我们根据有限的资源来衡量成本:开发人员和时间。这个责任不是上帝赋予的;它是由开发者部门的副总裁强加的。 C# 团队会以某种方式忽略预算流程的想法很奇怪。我向你保证,权衡过去和现在都是经过仔细、深思熟虑的考虑,他们让 C# 社区表达了愿望、微软的战略使命以及他们自己在设计方面的卓越品味。【参考方案2】:

只有 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

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

【讨论】:

我相信微软的很多其他人也肯定知道。 ;) 但是,是的,您提到了一个主要原因,编译时类型无法确定,因为没有。语言规范的第 8.5.1 节特别强调了不允许在隐式类型变量声明中使用匿名函数的原因。 是的。更糟糕的是,对于 lambda,我们甚至不知道它是否是委托类型;它可能是一个表达式树。 对于任何感兴趣的人,我在mindscapehq.com/blog/index.php/2011/02/23/… 上写了更多关于此以及 C# 和 F# 如何处理对比的内容 为什么编译器不能像 C++ 为它的 lambda 函数创建一个新的独特类型 它们在“.NET 类型系统级别”有何不同?【参考方案3】:

Eric Lippert 在他所说的地方有一个旧的post

实际上是 C# 2.0 规范 呼吁这一点。方法组 表达式和匿名方法 表达式是无类型表达式 在 C# 2.0 中,lambda 表达式加入 它们在 C# 3.0 中。因此它是 让他们“裸体”出现是违法的 隐式的右手边 声明。

【讨论】:

语言规范的第 8.5.1 节强调了这一点。 “初始化表达式必须具有编译时类型”才能用于隐式类型的局部变量。【参考方案4】:

不同的委托被认为是不同的类型。例如,ActionMethodInvoker 不同,Action 的实例不能分配给 MethodInvoker 类型的变量。

那么,给定一个像 () =&gt; 这样的匿名委托(或 lambda),它是 Action 还是 MethodInvoker?编译器无法判断。

同样,如果我声明一个接受string 参数并返回bool 的委托类型,编译器怎么会知道你真的想要一个Func&lt;string, bool&gt; 而不是我的委托类型?它无法推断委托类型。

【讨论】:

【参考方案5】:

以下几点来自 MSDN 关于隐式类型局部变量:

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

MSDN Reference: Implicitly Typed Local Variables

关于匿名方法考虑以下几点:

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

MSDN Reference: Anonymous Methods

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

【讨论】:

【参考方案6】:

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

“我如何避免输入一些像Func&lt;string, string, int, CustomInputType, bool, ReturnType&gt; 这样的丑陋类型?” [1]

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

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

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&lt;dynamic&gt;

是的,我知道这可能违反了您的编程原则,但这对我和一些 Python 编码员来说是有意义的。

我对代表很陌生...只是想分享我学到的东西。


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

【讨论】:

【参考方案7】:

怎么样?

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

【讨论】:

以上是关于为啥不能将匿名方法分配给 var?的主要内容,如果未能解决你的问题,请参考以下文章

samba能否实现一个文件夹可匿名访问另一个文件夹不可匿名访问?

为啥在匿名类中只能访问最终变量?

如何按值将变量传递给匿名javascript函数?

为啥局部函数生成的 IL 不同于匿名方法和 Lambda 表达式?

匿名方法

为啥 Set.add() 方法在匿名 Runnable 中不起作用?