【发布时间】:2011-05-16 01:21:42
【问题描述】:
这有点啰嗦,所以这里是快速版本:
为什么这会导致运行时 TypeLoadException?(编译器是否应该阻止我这样做?)
interface I
{
void Foo<T>();
}
class C<T1>
{
public void Foo<T2>() where T2 : T1 { }
}
class D : C<System.Object>, I { }
如果您尝试实例化 D,则会发生异常。
更长、更具探索性的版本:
考虑:
interface I
{
void Foo<T>();
}
class C<T1>
{
public void Foo<T2>() where T2 : T1 { }
}
class some_other_class { }
class D : C<some_other_class>, I { } // compiler error CS0425
这是非法的,因为C.Foo() 上的类型约束与I.Foo() 上的类型约束不匹配。它会生成编译器错误 CS0425。
但我认为我可能能够打破规则:
class D : C<System.Object>, I { } // yep, it compiles
通过使用Object 作为 T2 的约束,我否定该约束。我可以安全地将任何类型传递给D.Foo<T>(),因为一切都源自Object。
即便如此,我仍然希望得到一个编译器错误。在 C# 语言 的意义上,它违反了“C.Foo() 上的约束必须与 I.Foo() 上的约束相匹配”的规则,我认为编译器将是规则。但它确实编译。似乎编译器看到了我在做什么,理解它是安全的,然后视而不见。
我以为我已经成功了,但运行时说没那么快。如果我尝试创建 D 的实例,我会收到 TypeLoadException:“类型 'D' 上的方法 'C`1.Foo' 试图隐式实现具有较弱类型参数约束的接口方法。”
但是这个错误在技术上不是错误的吗?将Object 用于C<T1> 是否会否定C.Foo() 的约束,从而使其等效于-不强于-I.Foo()?编译器似乎同意,但运行时不同意。
为了证明我的观点,我通过将D 排除在等式之外来简化它:
interface I<T1>
{
void Foo<T2>() where T2 : T1;
}
class some_other_class { }
class C : I<some_other_class> // compiler error CS0425
{
public void Foo<T>() { }
}
但是:
class C : I<Object> // compiles
{
public void Foo<T>() { }
}
这对于传递给Foo<T>() 的任何类型都可以完美编译和运行。
为什么?运行时是否存在错误,或者(更有可能)我没有看到导致此异常的原因 - 在这种情况下,编译器不应该阻止我吗?
有趣的是,如果通过将约束从类移动到接口来反转场景......
interface I<T1>
{
void Foo<T2>() where T2 : T1;
}
class C
{
public void Foo<T>() { }
}
class some_other_class { }
class D : C, I<some_other_class> { } // compiler error CS0425, as expected
我再次否定了约束:
class D : C, I<System.Object> { } // compiles
这次运行正常!
D d := new D();
d.Foo<Int32>();
d.Foo<String>();
d.Foo<Enum>();
d.Foo<IAppDomainSetup>();
d.Foo<InvalidCastException>();
任何事情都会发生,这对我来说很有意义。 (等式中有或没有D 相同)
那么为什么第一种方式会中断?
附录:
我忘了补充一点,TypeLoadException 有一个简单的解决方法:
interface I
{
void Foo<T>();
}
class C<T1>
{
public void Foo<T2>() where T2 : T1 { }
}
class D : C<Object>, I
{
void I.Foo<T>()
{
Foo<T>();
}
}
显式实现I.Foo() 很好。只有隐式实现会导致 TypeLoadException。现在我可以这样做了:
I d = new D();
d.Foo<any_type_i_like>();
但这仍然是一个特例。尝试使用 System.Object 以外的任何其他内容,这将无法编译。我觉得这样做有点脏,因为我不确定它是否故意这样工作。
【问题讨论】:
-
并非所有类型都真正继承自
Object。所有堆实例都是派生自Object的类型,但值类型存储位置仅保存该类型的字段(公共和私有),没有任何附加类型信息。这样的字段集合可以隐式转换为Object,但不是一个。请注意,将某些内容约束到Object类型的泛型类型参数会有效地添加class约束。进一步注意,具有接口约束和class约束的通用参数将接受该接口的struct实现,如果它们是... -
...在传递之前转换为堆对象。
标签: c# generics clr runtime compiler-bug