为什么这个generics场景会导致TypeLoadException?

这有点啰嗦,所以这是快速版本:

为什么这会导致运行时TypeLoadException? (并且编译器是否应该阻止我这样做?)

interface I { void Foo(); } class C { public void Foo() where T2 : T1 { } } class D : C, I { } 

如果您尝试实例化D,则会发生exception。


更长,更具探索性的版本:

考虑:

 interface I { void Foo(); } class C { public void Foo() where T2 : T1 { } } class some_other_class { } class D : C, I { } // compiler error CS0425 

这是非法的,因为C.Foo()的类型约束与C.Foo()上的类型约束不匹配。 它会生成编译器错误CS0425。

但我以为我可以打破这个规则:

 class D : C, I { } // yep, it compiles 

通过使用Object作为T2的约束,我正在否定该约束。 我可以安全地将任何类型传递给D.Foo() ,因为所有类型都来自Object

即便如此,我仍然期望得到编译器错误。 在C# 语言意义上,它违反了“C.Foo()上的约束必须与I.Foo()上的约束相匹配的规则”,我认为编译器将成为规则的坚持者。 但它确实编译。 似乎编译器看到了我正在做的事情,理解它是安全的,并且视而不见。

我以为我已经离开了它,但是运行时说不那么快 。 如果我尝试创建D的实例,我会得到一个TypeLoadException:类型’D’上的’方法’C`1.Foo’试图隐式地实现具有较弱类型参数约束的接口方法。“

但这不是技术错误吗? 不使用Object for C否定对C.Foo()的约束,从而使它等于 – 不强于 – I.Foo() ? 编译器似乎同意,但运行时没有。

为了certificate我的观点,我通过将D取出等式来简化它:

 interface I { void Foo() where T2 : T1; } class some_other_class { } class C : I // compiler error CS0425 { public void Foo() { } } 

但:

 class C : I // compiles { public void Foo() { } } 

这适用于传递给Foo()任何类型的编译和运行。

为什么? 运行时是否存在错误,或者(更有可能)是否存在我未看到此exception的原因 – 在哪种情况下编译器不应该阻止我?

有趣的是,如果通过将约束从类移动到接口来反转场景…

 interface I { void Foo() where T2 : T1; } class C { public void Foo() { } } class some_other_class { } class D : C, I { } // compiler error CS0425, as expected 

我再次否定了这个限制:

 class D : C, I { } // compiles 

这次运行正常!

 D d := new D(); d.Foo(); d.Foo(); d.Foo(); d.Foo(); d.Foo(); 

一切顺利,这对我来说非常有意义。 (等式中有或没有D

那么为什么第一种方式会破裂呢?

附录:

我忘了补充说TypeLoadException有一个简单的解决方法:

 interface I { void Foo(); } class C { public void Foo() where T2 : T1 { } } class D : C, I { void I.Foo() { Foo(); } } 

明确地实现I.Foo()很好。 只有隐式实现会导致TypeLoadException。 现在我可以这样做:

  I d = new D(); d.Foo(); 

但它仍然是一个特例。 尝试使用除System.Object之外的任何其他内容,这将无法编译。 我觉得这样做有点脏,因为我不确定它是否故意以这种方式运作。

这是一个错误 – 请参阅从通用接口实现通用方法导致TypeLoadException和Unverifiable Code with Generic Interface和Generic Method with Type Parameter Constraint 。 但是,我不清楚它是C#bug还是CLR bug。

[由OP添加:]

以下是微软在您链接的第二个post中所说的内容(我的重点):

运行时使用的算法与C#编译器之间存在不匹配,以确定一组约束是否与另一组一样强。 这种不匹配导致C#编译器接受运行时拒绝的一些构造,结果是您看到的TypeLoadException。 我们正在调查以确定此代码是否是该问题的表现。 无论如何, 编译器接受这样的代码导致运行时exception肯定不是“按设计”

问候,

Ed Maurer C#编译器开发主管

从我加粗的部分,我认为他说这是一个编译器错误。 那是在2007年。我想这不足以成为他们解决问题的优先事项。

唯一的解释是约束被认为是方法声明的一部分。 这就是为什么在第一种情况下它是编译器错误。

编译器在使用object时没有收到错误……好吧, 那是编译器的错误

其他“约束”具有与通用约束相同的属性:

 interface I { object M(); } class C { public some_type M() { return null; } } class D : C, I { } 

我可以问:为什么这不起作用?

你看? 这和你的问题完全一样。 使用some_type实现object是完全有效的,但运行时和编译器都不会接受它。

如果您尝试生成MSIL代码,并强制执行我的示例,则运行时会抱怨。

隐式接口实现要求方法声明的generics约束是等效的,但在代码中不一定完全相同。 另外,generics类型参数具有“where T:object”的隐式约束。 这就是为什么指定C编译,它会使约束变得等同于接口中的隐式约束。 ( C#语言规范第13.4.3节)。

您也可以使用调用约束方法的显式接口实现。 它提供了从接口方法到约束无法区分的类中的实现的非常清晰的映射,然后继续调用类似命名的generics方法(现在与接口无关)。 此时,可以使用与任何通用方法调用相同的方式解决对辅助方法的约束,而不会出现任何接口解析问题。

在第二个示例中,将约束从类移动到接口更好,因为默认情况下类将从接口中获取约束。 这也意味着您必须在类实现中指定约束(如果适用)(对于Object,它不适用)。 传递I意味着您无法在代码中直接指定该约束(因为字符串是密封的),因此它必须是显式接口实现的一部分,或者是两个地方中约束的generics类型。

据我所知,运行时和编译器对约束使用单独的validation系统。 编译器允许这种情况,但运行时validation程序不喜欢它。 我想强调一点,我不确定为什么它有这个问题,但我猜它不喜欢该类定义中的潜力不能满足接口约束,这取决于最终设置的T至。 如果其他人对此有明确的答案,那就太好了。

响应基于接口的代码段:

 interface I { void Foo() where T2 : T1; } class C : I // compiler error CS0425 { public void Foo() { } } 

我相信问题在于编译器正在认识到:

  1. 你还没有在C.Foo()上声明必要的类型约束。
  2. 如果选择字符串作为类型,则C.Foo()上没有有效的T,因为类型不能从字符串inheritance。

要在实践中看到这项工作,请指定一个可以作为T1inheritance的实际类。

 interface I { void Foo() where T2 : T1; } class C : I { public void Foo() where T : MyClass { } } public class MyClass { } 

要显示字符串类型没有被处理为特殊,只需将sealed关键字添加到上面的MyClass声明中,如果要将T1指定为字符串以及将字符串指定为C上的类型约束,则以相同的方式查看它是否失败。美孚()。

 public sealed class MyClass { } 

这是因为字符串是密封的,不能形成约束的基础。