为什么C#编译器在这段代码上崩溃?

26

为什么下面的代码会导致.NET编译器崩溃?它已经在版本4.0的csc.exe上进行了测试。

例如,这里有一个在线演示以展示不同版本的情况——它以相同的方式崩溃,同时说动态不受支持:https://dotnetfiddle.net/FMn59S

 

编译错误(第0行,第0列):内部编译器错误(地址xy处的0xc0000005):可能的罪魁祸首是“TRANSFORM”。

但是对于List<dynamic>类型,扩展方法可以正常工作。

using System;
using System.Collections.Generic;

static class F  {
    public static void M<T>(this IEnumerable<T> enumeration, Action<T> action){}

    static void U(C.K d) {
        d.M(kvp => Console.WriteLine(kvp));
    }
}

class C  {
    public class K : Dictionary<string, dynamic>{}
}

更新:这不会导致编译器崩溃

static void U(Dictionary<string, dynamic> d)
{
    d.M(kvp => Console.WriteLine(kvp));
}

更新2:在http://connect.microsoft.com/VisualStudio/feedback/details/892372/compiler-error-with-dynamic-dictinoaries中,有人报告了相同的错误。该错误是针对FirstOrDefault报告的,但似乎编译器会在应用于继承自Dictionary<T1,T2>类且至少有一个参数类型为dynamic的任何扩展方法时崩溃。请参见Erik Funkenbusch下面更一般的问题描述。

更新3:另一种非标准的行为。当我尝试将扩展方法作为静态方法调用时,即F.M(d,kvp => Console.WriteLine(kvp));,编译器不会崩溃,但无法找到重载:Argument 1: cannot convert from 'C.K' to 'System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string,dynamic>>'

更新4-解决方案(有点):Hans勾画了第二个解决方法,它与原始代码在语义上等价,但仅适用于扩展方法调用而不适用于标准调用。由于该错误可能是由于编译器无法将派生自具有多个参数(其中一个为dynamic)的泛型类的类强制转换为其超类型所致,因此解决方案是提供显式转换。请参见https://dotnetfiddle.net/oNvlcL

((Dictionary<string, dynamic>)d).M(kvp => Console.WriteLine(kvp));
M((Dictionary<string, dynamic>)d, kvp => Console.WriteLine(kvp));

40
很好的类名选择。 - H H
8
就我所知,Roslyn可以对此进行编译。 - Jon Skeet
2
@Henk Holterman 看起来这个 bug 是一样的 - FirstOrDefault 也是扩展方法 http://connect.microsoft.com/VisualStudio/feedback/details/892372/compiler-error-with-dynamic-dictinoaries - Franta
3
我很难弄清楚什么答案能够回答这个问题:“为什么一个我没有源代码的程序会崩溃?”你想从这个问题的任何答案中学到什么? - Eric Lippert
2
@Eric - 我希望能够了解编译器的内部工作原理,并找出这个错误的实际范围,因为它在比我最初想象的情况下表现出来。 - Franta
显示剩余8条评论
2个回答

18

当你用对象替换动态的时,它会触发不稳定性,而替换后崩溃就消失了。

这是其中一种解决方法,另一种方法是帮助它推断正确的T值:

static void U(C.K d) {
    d.M(new Action<KeyValuePair<string, dynamic>>(kvp => Console.WriteLine(kvp)));
}

您找到的反馈报告已经非常匹配,我认为无需提交自己的报告。

更不用说这个问题已经在内部版本中得到了修复,并且似乎在Roslyn中也得到了修复。https://connect.microsoft.com/VisualStudio/feedback/details/814645/calling-todictionary-on-a-class-that-inherits-from-dictionary-that-has-a-dynamic-value-causes-compiler-crash - Erik Funkenbusch
如果你正在编写解释器,那么没有使用dynamic会更加困难。目前我知道的最好的解决方法是定义一个期望Dictionary<T, T2>的方法,但这也是一个相当糟糕的解决方案。当需要将具有多个参数的一个泛型类强制转换为另一个接口,并且至少有一个参数是动态时,似乎会出现bug。在一个情况下它会崩溃编译器(扩展方法),在另一个情况下它找不到重载。 - Franta
嗯,我的解决方法完全合理,只需要帮助它推断类型。在使用dynamic时可以很好地工作,不需要选择“T2”。你为什么不想使用它呢? - Hans Passant
请原谅我的无知,您的第二个解决方法很好。使用委托很有效。如果编译器没有真正的答案,说明出了什么问题,我将选择此作为答案。因此,如果只有lambda参数,编译器似乎会将类转换为错误的接口? - Franta
4
在这段代码中,C#编译器面临一个棘手的问题,双重类型推断和dynamic可以互换,因为Dictionary实现了协变接口,这会导致它必须考虑的可能匹配数量爆炸。我不想自己写这样的代码,因为它无法完全测试。潜在的崩溃只是NullReferenceException,在C#程序中每天都会发生,但当它发生在C++中时,看起来很难看。需要微软工程师才能完全诊断它。简化它所要处理的任务,崩溃就会消失。 - Hans Passant
@HansPassant - 我尝试将您的第二个解决方法应用于Update 3中的情况,即调用静态方法(F.M(d, new Action<KeyValuePair<string, dynamic>>(kvp => Console.WriteLine(kvp))); - https://dotnetfiddle.net/RBNzOW),但它并没有起到帮助作用。还有其他解决方法吗? - Franta

15

那么,关于你的问题为什么会导致编译器崩溃的答案是,因为你遇到了一个会……崩溃编译器的 bug。

VS2013编译器显示“内部编译器错误(地址012DC5B5处的0xc0000005):可能的罪魁祸首是'TRANSFORM'”,因此很明显这是个bug。

C0000005通常是空指针,或者引用未分配或已删除的内存。这是一种通用保护错误。

编辑:

该问题在任何多参数通用类型中也存在,其中任何参数都是动态的。例如,在以下情况下会导致崩溃:

List<Tuple<string, dynamic>>{}

它还在某些情况下会崩溃。

List<KeyValuePair<dynamic, string>>{}

但不会崩溃

List<dynamic>{}

但会在此处崩溃

List<List<dynamic>>{}

7
所以,这是问题的重复吗?回答部分在哪里? - H H
9
@HenkHolterman - 首先,在我回答问题时,问题并没有包含同样的信息。他事后添加了这些信息。其次,回答是第一句话,明确地说“你的问题的答案”。我知道听起来很蠢,但那就是答案。问一个愚蠢的问题,就会得到一个愚蠢的答案。我们没有编译器的源代码,所以我们无法知道更多。 - Erik Funkenbusch
3
你可能想将这些信息添加到问题中,但它并没有真正回答问题。 - CodeCaster
@ErikFunkenbusch - 只是想澄清一下,我对0n13/14中断和SEH/VEH有一般的了解,但这只是某些问题的表现。Hans的第二种解决方法更具信息性,可以更好地了解该错误的本质。 - Franta

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