为什么必须对后代类型重新声明泛型类型限制?
在 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()" */
In C#, given a generic type such as this:
interface IGenericType<T> where T : new()
And a descendant type, 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()" */
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
在我看来,编译器可能足够聪明,可以从理论上推断出这些限制。但它不应该这么聪明,因为过于聪明的编译器有时是危险的。开发人员总是需要对所有事物都有清晰/明确的定义。请看这个场景:
(1) 有一个接口 IFoo;其中 T : new()
(2) 一个 Foo类; : IFoo和
new()
约束由编译器自动添加(太棒了!)(3) 类
Foo
是一个整个项目中非常基类,class A: Foo
,然后class B; : A
...(4) 现在另一个开发人员很难通过查看类的定义来意识到存在这样的约束,他会得到奇怪的编译错误(好吧,这是可以接受的)。但是如果它们是通过反射调用的呢?有时程序是正确的,因为数据偶然满足了限制。
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 thenew()
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 thenclass 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.
编译器能够推断 U 必须可转换为 SomeOtherType 并且必须具有默认构造函数。它将为每个约束生成一个编译器错误:
仅实现其中一个接口时也会发生这种情况。该类必须成功实现两个接口才能进行编译:
或作为非泛型类型:
将类标记为实现接口并不是对类的泛型类型参数指定约束的方法;而是将类标记为实现接口。这是一种要求新类型参数存在这些约束或者所提供的类型满足这些约束的方式。
也许您可以这样想:接口是一组受约束的类,泛型类也是一组受约束的类。通用接口是一组受约束的通用类。当您说泛型类实现泛型接口时,您是在询问编译器:“这个泛型类是否严格位于该泛型接口指定的集合内?”您不仅仅是将它们作为一组进一步受限的类进行交叉。
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:
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:
or as a non-generic type:
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.
因为泛型类型限制是针对定义类的类型参数(示例中的
U
),所以从 CLR 的角度来看,它与接口的类型参数是不同的类型。类的类型参数不必是接口的实际类型参数。它甚至不需要是一个简单的类型,如下所示:
在这种情况下,编译器识别出
List
满足约束,因此不需要进一步的规范。但是,如果不了解泛型类型参数,编译器就会要求您显式声明它。将此与泛型方法的类似但不相同的行为进行比较是有启发性的。与实现接口的类一样,必须在声明中指定类型限制。有一个值得注意的例外:如果实现是显式的。事实上,当您尝试重新施加限制时,编译器会生成错误。
例如,给定一个接口,
实现该接口的两种正确方法是:
在
OneThing
中省略约束或将其插入到OtherThing
中会产生编译时错误。为什么我们在第一个实现中需要约束,而不是在第二个实现中?我想说的是,出于与上面提到的接口类型约束相同的原因:在第一个实现中,类型T
与接口方法上的类型参数没有关系,因此必须将其明确化使方法与接口方法相匹配。在第二个实现中,我们显式声明接口意味着类型参数 T 与接口中使用的类型参数完全相同。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:
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
the two correct ways to implement this interface are:
Leaving out the constraint in
OneThing
or iserting it inOtherThing
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 typeT
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 parameterT
is the exact same one that was used in the interface.