'委托'System.Action'不接受 0 个参数。'这是 C# 编译器错误(lambda 和两个项目)吗?
考虑下面的代码。看起来是完全有效的 C# 代码,对吗?
//Project B
using System;
public delegate void ActionSurrogate(Action addEvent);
//public delegate void ActionSurrogate2();
// Using ActionSurrogate2 instead of System.Action results in the same error
// Using a dummy parameter (Action<double, int>) results in the same error
// Project A
public static class Class1 {
public static void ThisWontCompile() {
ActionSurrogate b = (a) =>
{
a(); // Error given here
};
}
}
我收到编译器错误“委托‘操作’不接受 0 个参数。”使用 (Microsoft) C# 4.0 编译器在指示的位置处。请注意,您必须在不同的项目中声明 ActionSurrogate 才会出现此错误。
它变得更有趣:
// Project A, File 1
public static class Class1 {
public static void ThisWontCompile() {
ActionSurrogate b = (a) => { a(); /* Error given here */ };
ActionSurrogate c = (a) => { a(); /* Error given here too */ };
Action d = () => { };
ActionSurrogate c = (a) => { a(); /* No error is given here */ };
}
}
我在这里偶然发现了 C# 编译器错误吗?
请注意,对于喜欢使用 lambda 并尝试创建数据结构库以供将来使用的人来说,这是一个非常烦人的错误...(我)
编辑:删除了错误的情况。
为了实现这一目标,我复制并剥离了我原来的项目到最低限度。这实际上是我的新项目中的所有代码。
Consider the code below. Looks like perfectly valid C# code right?
//Project B
using System;
public delegate void ActionSurrogate(Action addEvent);
//public delegate void ActionSurrogate2();
// Using ActionSurrogate2 instead of System.Action results in the same error
// Using a dummy parameter (Action<double, int>) results in the same error
// Project A
public static class Class1 {
public static void ThisWontCompile() {
ActionSurrogate b = (a) =>
{
a(); // Error given here
};
}
}
I get a compiler error 'Delegate 'Action' does not take 0 arguments.' at the indicated position using the (Microsoft) C# 4.0 compiler. Note that you have to declare ActionSurrogate in a different project for this error to manifest.
It gets more interesting:
// Project A, File 1
public static class Class1 {
public static void ThisWontCompile() {
ActionSurrogate b = (a) => { a(); /* Error given here */ };
ActionSurrogate c = (a) => { a(); /* Error given here too */ };
Action d = () => { };
ActionSurrogate c = (a) => { a(); /* No error is given here */ };
}
}
Did I stumble upon a C# compiler bug here?
Note that this is a pretty annoying bug for someone who likes using lambdas a lot and is trying to create a data structures library for future use... (me)
EDIT: removed erronous case.
I copied and stripped my original project down to the minimum to make this happen. This is literally all the code in my new project.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
最终更新:
该错误已在 C# 5 中修复。对于给您带来的不便,再次表示歉意,并感谢您的报告。
原文分析:
我可以用命令行编译器重现该问题。它看起来确实像一个错误。这可能是我的错;对此感到抱歉。 (我编写了所有 lambda 到委托转换检查代码。)
我现在在一家咖啡店,无法从这里访问编译器源代码。明天我会尝试找一些时间在调试版本中重现这一点,看看我是否能弄清楚发生了什么。如果我没有时间,我就会离开办公室直到圣诞节之后。
您观察到引入 Action 类型的变量会导致问题消失,这一点非常有趣。出于性能原因和语言规范所需的分析,编译器维护许多缓存。 Lambda 表达式和局部变量尤其具有大量复杂的缓存逻辑。我愿意打赌,这里有些缓存被初始化或填充错误,并且局部变量的使用在缓存中填充了正确的值。
感谢您的报告!
更新:我现在在公交车上,它突然出现在我面前;我想我很清楚出了什么问题。编译器是懒的,特别是在处理来自元数据的类型时。原因是引用的程序集中可能有数十万种类型,无需加载所有类型的信息。您可能会使用其中的不到 1%,因此我们不要浪费大量时间和内存来加载您永远不会使用的内容。事实上,懒惰的含义远不止于此。类型在使用之前要经过几个“阶段”。首先它的名称是已知的,然后是它的基类型,然后是它的基类型层次结构是否是有根据的(非循环等),然后是它的类型参数约束,然后是它的成员,然后是成员是否是有根据的(覆盖覆盖某些东西)具有相同的签名,依此类推。)我敢打赌,转换逻辑无法调用“确保所有委托参数的类型都知道其成员”的方法。它检查委托调用的签名的兼容性。但是创建局部变量的代码可能确实做到了这一点。我认为在转换检查期间,就编译器而言,Action 类型甚至可能没有调用方法。
我们很快就会知道。
更新:今天早上我的心灵力量很强。当重载解析尝试确定是否存在接受零个参数的委托类型的“Invoke”方法时,它会发现零个 Invoke 方法可供选择。在进行重载解析之前,我们应该确保委托类型元数据已完全加载。奇怪的是,这么长时间以来,这件事却没有引起人们的注意;它在 C# 3.0 中重现。当然,它不会在 C# 2.0 中重现,因为没有 lambda; C# 2.0 中的匿名方法要求您显式声明类型,这会创建一个本地变量,我们知道它会加载元数据。但我认为该错误的根本原因(重载解析不会强制加载调用的元数据)可以追溯到 C# 1.0。
不管怎样,这是一个令人着迷的错误,感谢您的报告。显然你有一个解决方法。我会让 QA 从这里跟踪它,我们将尝试针对 C# 5 修复它。(我们错过了 Service Pack 1,已处于测试阶段。)
FINAL UPDATE:
The bug has been fixed in C# 5. Apologies again for the inconvenience, and thanks for the report.
Original analysis:
I can reproduce the problem with the command-line compiler. It certainly looks like a bug. It's probably my fault; sorry about that. (I wrote all of the lambda-to-delegate conversion checking code.)
I'm in a coffee shop right now and I don't have access to the compiler sources from here. I'll try to find some time to reproduce this in the debug build tomorrow and see if I can work out what's going on. If I don't find the time, I'll be out of the office until after Christmas.
Your observation that introducing a variable of type Action causes the problem to disappear is extremely interesting. The compiler maintains many caches for both performance reasons and for analysis required by the language specification. Lambdas and local variables in particular have lots of complex caching logic. I'd be willing to bet as much as a dollar that some cache is being initialized or filled in wrong here, and that the use of the local variable fills in the right value in the cache.
Thanks for the report!
UPDATE: I am now on the bus and it just came to me; I think I know exactly what is wrong. The compiler is lazy, particularly when dealing with types that came from metadata. The reason is that there could be hundreds of thousands of types in the referenced assemblies and there is no need to load information about all of them. You're going to use far less than 1% of them probably, so let's not waste a lot of time and memory loading stuff you're never going to use. In fact the laziness goes deeper than that; a type passes through several "stages" before it can be used. First its name is known, then its base type, then whether its base type hierarchy is well-founded (acyclic, etc), then its type parameter constraints, then its members, then whether the members are well-founded (that overrides override something of the same signature, and so on.) I'll bet that the conversion logic is failing to call the method that says "make sure the types of all the delegate parameters have their members known", before it checks the signature of the delegate invoke for compatibility. But the code that makes a local variable probably does do that. I think that during the conversion checking, the Action type might not even have an invoke method as far as the compiler is concerned.
We'll find out shortly.
UPDATE: My psychic powers are strong this morning. When overload resolution attempts to determine if there is an "Invoke" method of the delegate type that takes zero arguments, it finds zero Invoke methods to choose from. We should be ensuring that the delegate type metadata is fully loaded before we do overload resolution. How strange that this has gone unnoticed this long; it repros in C# 3.0. Of course it does not repro in C# 2.0 simply because there were no lambdas; anonymous methods in C# 2.0 require you to state the type explicitly, which creates a local, which we know loads the metadata. But I would imagine that the root cause of the bug - that overload resolution does not force loading metadata for the invoke - goes back to C# 1.0.
Anyway, fascinating bug, thanks for the report. Obviously you've got a workaround. I'll have QA track it from here and we'll try to get it fixed for C# 5. (We have missed the window for Service Pack 1, which is already in beta.)
这可能是类型推断的问题,显然编译器将
a
推断为Action
而不是Action
(它可能认为>a
是ActionSurrogate
,它符合Action>
签名)。尝试显式指定a
的类型:如果不是这种情况 - 可能会检查您的项目是否有任何自定义的
Action
委托采用一个参数。This probably is a problem with type inference, apperently the compiler infers
a
as anAction<T>
instead ofAction
(it might thinka
isActionSurrogate
, which would fit theAction<Action>>
signature). Try specifying the type ofa
explicitly:If this is not the case - might check around your project for any self defined
Action
delegates taking one parameter.这将编译。编译器出现一些故障,无法找到没有参数的 Action 委托。这就是您收到错误的原因。
This will compile. Some glitch with the compiler its unable to find the Action delegate without parameters. That's why you are getting the error.