在C#中,给出一个通用类型,如:
interface IGenericType<T> where T : new()
和后代类型,例如:
class GenericTypeImplementation<U> : IGenericType<U>
为什么我们需要使用父类型的所有限制明确限制泛型类型U
?
class GenericTypeImplementation<U> : IGenericType<U> where U : new()
我是否正确地推断出问题在于编译器计算限制的结合?
interface IGenericType<T> where T : new()
interface IGenericType2<T> where T : SomeOtherType
class GenericTypeImplementation<U> : IGenericType<U>, IGenericType2<U>
/* Hypothesis: Compiler can't infer U must be "SomeOtherType + new()" */
答案 0 :(得分:2)
在我看来,编译器可以足够聪明,从理论上推断出这些限制。但它不应该如此聪明,因为过于聪明的编译器有时会很危险。开发人员总是需要明确/明确地定义所有内容。请参阅此方案:
(1)有一个interface IFoo<T> where T : new()
(2)编译器自动添加class Foo<T> : IFoo<T>
和new()
约束(非常棒!)
(3)类Foo<T>
是整个项目中的一个基类class A<T> : Foo<T>
,然后是class B<T> : A<T>
......
(4)现在另一个开发人员很难通过查看类的定义来实现这样的约束,他会得到奇怪的编译错误(这是可以接受的)。但如果它们被反射调用呢?有时程序是正确的,因为数据是偶然遇到限制的。
答案 1 :(得分:1)
编译器能够推断出U必须可以转换为SomeOtherType,并且必须具有默认构造函数。它将为每个约束生成编译器错误:
Error 1 The type 'U' must have a public parameterless constructor in order to use it as parameter 'T' in the generic type or method '....IGenericType<T>'
Error 2 The type 'U' must be convertible to '....SomeOtherType' in order to use it as parameter 'T' in the generic type or method '....IGenericType2<T>'
只有其中一个接口也可以实现。该类必须成功实现两个接口才能进行编译:
class GenericTypeImplementation<U> : IGenericType<U>, IGenericType2<U>
where U : SomeOtherType, new()
{...}
或作为非泛型类型:
class GenericTypeImplementation : IGenericType<SomeType>, IGenericType2<SomeOtherType>
{...}
将类标记为实现接口不是指定类的泛型类型参数的约束的方法;这是一种要求这些约束存在于新类型参数上的方式,或者它们是由提供的类型满足的。
也许你可以这样想:接口是一组受约束的类,泛型类也是一组受约束的类。通用接口是一组受限制的泛型类。当你说泛型类实现泛型接口时,你会问编译器,“这个泛型类是否严格在这个泛型接口指定的集合中?”您不仅仅将它们作为一组进一步约束的类交叉。
答案 2 :(得分:1)
因为泛型类型限制在定义类的类型参数(在您的示例中为U
),从CLR的角度来看,这是与接口的类型参数不同的类型。
类的类型参数不必是接口的实际类型参数。它甚至不需要是一个简单的类型,如:
class Implementation<T> : IGenericType<List<T>> { /* ... */ }
在这种情况下,编译器识别出List<T>
满足约束,因此不需要进一步的指定。但是如果没有关于泛型类型参数的知识,编译器需要您明确声明它。
将此与通用方法的相似但不相同的行为进行比较是有益的。与实现接口的类一样,必须使用声明指定类型限制。有一个值得注意的例外:如果实现是明确的。实际上,当您尝试重新施加限制时,编译器将生成错误。
例如,给定一个接口
interface ISomething {
void DoIt<T>() where T : new();
}
实现此接口的两种正确方法是:
class OneThing : ISomething {
public void DoIt<T>() where T : new() { }
}
class OtherThing : ISomething {
void ISomething.DoIt<T>() { }
}
在OneThing
中省略约束或在OtherThing
中使用约束会产生编译时错误。为什么我们需要第一个实现中的约束而不是第二个实现中的约束?我上面提到的与上面提到的接口类型约束的原因相同:在第一个实现中,类型T
与接口方法上的类型参数没有关系,因此必须对方法明确说明匹配接口方法。在第二个实现中,我们显式声明接口意味着类型参数T
与接口中使用的完全相同。