开发者

Why do generic type restrictions have to be redeclared on descendant types?

In C#, given a generic type such as this:

interface IGenericType<T> where T : new()

And a descendant ty开发者_开发百科pe, such as:

class GenericTypeImplementation<U> : IGenericType<U>

Why do we need to explicitly restrict the generic type U with all the restrictions of the parent type?

class GenericTypeImplementation<U> : IGenericType<U> where U : new()

Am I right in inferring that the issue is in the compiler computing the union of restrictions?

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


In my opinion, the compiler could be smart enough to infer the restrictions theoretically. But it shouldn't be so smart, because a too-smart compiler is sometimes dangerous. Developers always need a clear/explicit definition of everything. See this scenario:

(1) there is an interface IFoo<T> where T : new()

(2) a class Foo<T> : IFoo<T> and the new() constraint is added automatically by the compiler(brilliant!)

(3) the class Foo<T> is a very base class in the whole project, class A<T> : Foo<T>, and then class B<T> : A<T>...

(4) Now another developer can hardly realize there is such a constraint by looking into the definition of the class, he will get weird compiling errors(well that's acceptable). But what if they are invoked by reflection? Sometimes the program is correct, because the data meets the restriction by accident.


The compiler is able to to infer that U must be convertible to SomeOtherType and must have a default constructor. It will generate a compiler error for each constraint:

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>'

This will also happen with just one of those interfaces implemented as well. The class must successfully implement both interfaces in order to be compiled:

class GenericTypeImplementation<U> : IGenericType<U>, IGenericType2<U>
    where U : SomeOtherType, new()
{...}

or as a non-generic type:

class GenericTypeImplementation : IGenericType<SomeType>, IGenericType2<SomeOtherType>
{...}

To mark a class as implementing an interface is not a way of specifying constraints on the generic type parameters of a class; it is a way of requiring that those constraints exist on a new type parameter or that they be satisfied by a supplied type.

Perhaps you could think of it this way: an interface is a constrained set of classes and a generic class is also a constrained set of classes. A generic interface is a constrained set of generic classes. When you say that a generic class implements a generic interface, you are asking the compiler, "Is this generic class strictly within the set specified by this generic interface?" You are not merely intersecting them as a further constrained set of classes.


Because a generic type restriction is on the type parameter of the defining class (U in your example), from a CLR point of view, that is a different type from the type parameter of the interface.

The type parameter of the class need not be the actual type parameter of the interface. It need not even be a simple type, as in:

class Implementation<T> : IGenericType<List<T>> { /* ... */ }

In this case, the compiler recognizes that List<T> satisfies the constraint, and so no further specification is necessary. But without such knowledge about the generic type parameter, the compiler requires you to declare it explicitly.

It is instructive to compare this to the similar but not identical behaviour of generic methods. As with classes that implement interfaces, the type restrictions must be specified with the declaration. There is one notable exception: if the implementation is explicit. In fact, the compiler will generate an error when you try to re-impose the restrictions.

For example, given an interface

interface ISomething {
    void DoIt<T>() where T : new();
}

the two correct ways to implement this interface are:

class OneThing : ISomething {
    public void DoIt<T>() where T : new() { }
}

class OtherThing : ISomething {
    void ISomething.DoIt<T>() { }
}

Leaving out the constraint in OneThing or iserting it in OtherThing produces a compile-time error. Why do we need the constraint in the first implementation and not in the second one? I'd say for the same reason I mentioned above for type constraints on interfaces: in the first implementation, the type T has no relation to the type parameter on the interface method, so it must be made explicit for the method to match the interface method. In the second implementation, the fact that we explicitly declare the interface means that the type parameter T is the exact same one that was used in the interface.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜