为什么必须在后代类型上重新声明泛型类型限制?

时间:2011-10-12 02:35:18

标签: c# generics types

在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()" */

3 个答案:

答案 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与接口中使用的完全相同。