44

考虑下面的代码。看起来像完全有效的 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# 编译器错误吗?

请注意,对于非常喜欢使用 lambdas 并试图创建一个数据结构库以供将来使用的人来说,这是一个非常烦人的错误......(我)

编辑:删除了错误的案例。

我复制了我的原始项目并将其剥离到最低限度以实现这一目标。这实际上是我的新项目中的所有代码。

4

3 回答 3

67

最后更新:

该错误已在 C# 5 中修复。再次为给您带来的不便深表歉意,并感谢您的报告。


原文分析:

我可以用命令行编译器重现这个问题。它肯定看起来像一个错误。这可能是我的错;对于那个很抱歉。(我编写了所有 lambda 到委托的转换检查代码。)

我现在在一家咖啡店,我无法从这里访问编译器源。明天我会试着找一些时间在调试版本中重现这个,看看我是否能弄清楚发生了什么。如果我没有时间,我会在圣诞节后离开办公室。

您观察到引入 Action 类型的变量会导致问题消失,这非常有趣。出于性能原因和语言规范要求的分析,编译器维护许多缓存。Lambda 和局部变量尤其具有许多复杂的缓存逻辑。我愿意赌一美元在这里初始化或填写错误的缓存,并且使用局部变量在缓存中填写正确的值。

感谢您的报告!

更新:我现在在公共汽车上,它刚刚来到我身边;我想我确切地知道出了什么问题。编译器很懒,尤其是在处理来自元数据的类型时。原因是引用的程序集中可能有数十万种类型,无需加载所有这些类型的信息。您可能会使用不到 1% 的内容,所以我们不要浪费大量时间和内存来加载您永远不会使用的东西。事实上,懒惰比这更深。一个类型在使用之前要经过几个“阶段”。首先它的名称是已知的,然后是它的基类型,然后是它的基类型层次结构是否有根据(非循环等),然后是它的类型参数约束,然后是它的成员,然后是成员是否有根据(覆盖覆盖某些东西)签名相同,依此类推。)我敢打赌,转换逻辑无法调用“他们的成员已知”,在它检查委托调用的签名以确保兼容性之前。但生成局部变量的代码可能会这样做。我认为在转换检查期间,Action 类型甚至可能没有调用方法就编译器而言。

我们很快就会知道的。

更新:今天早上我的精神力量很强大。当重载决议尝试确定是否存在接受零参数的委托类型的“Invoke”方法时,它会找到零个可供选择的 Invoke 方法。在进行重载解析之前,我们应该确保委托类型元数据已完全加载。这么长时间没人注意到这件事是多么奇怪;它在 C# 3.0 中重现。当然,它不会仅仅因为没有 lambda 而在 C# 2.0 中重现;C# 2.0 中的匿名方法要求您显式声明类型,这会创建一个本地,我们知道它会加载元数据。但我想这个错误的根本原因 - 重载解决方案不会强制为调用加载元数据 - 可以追溯到 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.)

于 2010-12-17T02:14:00.593 回答
24

这可能是类型推断的问题,显然编译器推断aAction<T>而不是Action(它可能认为ais ActionSurrogate,这将适合Action<Action>>签名)。尝试明确指定类型a

    ActionSurrogate b = (Action a) =>
                        {
                            a();
                        };

如果不是这种情况 - 可能会检查您的项目是否有任何Action采用一个参数的自定义委托。

于 2010-12-17T01:09:52.400 回答
2
    public static void ThisWontCompile()
        {
            ActionSurrogate b = (Action a) =>
            {
                a();
            };


        }

这将编译。编译器出现了一些故障,它无法找到没有参数的 Action 委托。这就是您收到错误的原因。

public delegate void Action();
public delegate void Action<T>();
public delegate void Action<T1,T2>();
public delegate void Action<T1,T2,T3>();
public delegate void Action<T1,T2,T3,T4>();
于 2010-12-17T01:45:42.067 回答