项目类
public class Item
{
public bool Check(int value) { ... }
}
具有泛型类型约束的基抽象类
public abstract class ClassBase<TItem>
where TItem : Item
{
protected IList<TItem> items;
public ClassBase(IEnumerable<TItem> items)
{
this.items = items.ToList();
}
public abstract bool CheckAll(int value);
}
没有约束的继承类
public class MyClass<TItem> : ClassBase<TItem>
{
public override bool CheckAll(int value)
{
bool result = true;
foreach(TItem item in this.items)
{
if (!item.Check(value)) // this doesn't work
{
result = false;
break;
}
}
return result;
}
}
我想知道为什么泛型类型约束不可继承?因为如果我的继承类继承自基类并传递对基类有约束的泛型类型,那么它自动意味着继承类中的泛型类型应该具有相同的约束,而无需显式定义它。不应该吗?
我是否做错了什么,理解错误,或者泛型类型约束真的不可继承吗?如果后者是真的,到底为什么会这样?
一些额外的解释
为什么我认为在类上定义的泛型类型约束应该在子类上继承或强制执行?让我给你一些额外的代码,让它不那么明显。
假设我们拥有上述所有三个类。然后我们还有这个类:
public class DanteItem
{
public string ConvertHellLevel(int value) { ... }
}
正如我们所看到的,这个类不是从 Item
继承的,因此它不能用作 ClassBase
那样的具体类(忘记事实上,ClassBase
目前是抽象的,它也可以是一个常规类)。由于 MyClass
没有为其泛型类型定义任何约束,因此拥有 MyClass
似乎完全有效...
但是。这就是为什么我认为泛型类型约束应该像成员泛型类型约束一样在继承的类上继承/强制执行,因为如果我们查看 MyClass
的定义,它会说:
MyClass<T> : ClassBase<T>
当 T
是 DanteItem
我们可以看到它自动不能与 MyClass
一起使用,因为它继承自 ClassBase
和 DanteItem< /code> 未满足其要求泛型类型约束。我可以说 MyClass
上的 **泛型类型取决于 ClassBase
泛型类型约束,因为否则 MyClass
可以用任何类型实例化。但我们知道不可能。
时,情况当然会有所不同
public class MyClass<T> : ClassBase<Item>
当我将 MyClass
定义为:在这种情况下,T 与基类的泛型类型没有任何关系,因此它独立于基类
。这是一个有点长的解释/推理。我可以简单地总结一下:
如果我们不对 MyClass
提供泛型类型约束,则隐式意味着我们可以使用任何具体类型实例化 MyClass
。但我们知道这是不可能的,因为 MyClass
是从 ClassBase
继承的,并且具有泛型类型约束。
我希望现在这更有意义。
Item class
public class Item
{
public bool Check(int value) { ... }
}
Base abstract class with generic type constraint
public abstract class ClassBase<TItem>
where TItem : Item
{
protected IList<TItem> items;
public ClassBase(IEnumerable<TItem> items)
{
this.items = items.ToList();
}
public abstract bool CheckAll(int value);
}
Inherited class without constraints
public class MyClass<TItem> : ClassBase<TItem>
{
public override bool CheckAll(int value)
{
bool result = true;
foreach(TItem item in this.items)
{
if (!item.Check(value)) // this doesn't work
{
result = false;
break;
}
}
return result;
}
}
I would like to know why aren't generic type constraints inheritable? Because if my inherited class inherits from base class and passes over its generic type which has a constraint on the base class it automatically means that generic type in inherited class should have the same constraint without explicitly defining it. Shouldn't it?
Am I doing something wrong, understanding it wrong or is it really that generic type constraint aren't inheritable? If the latter is true, why in the world is that?
A bit of additional explanation
Why do I think that generic type constraints defined on a class should be inherited or enforced on child classes? Let me give you some additional code to make it bit less obvious.
Suppose that we have all three classes as per above. Then we also have this class:
public class DanteItem
{
public string ConvertHellLevel(int value) { ... }
}
As we can see this class does not inherit from Item
so it can't be used as a concrete class as ClassBase<DanteItem>
(forget the fact that ClassBase
is abstract for now. It could as well be a regular class). Since MyClass
doesn't define any constraints for its generic type it seems perfectly valid to have MyClass<DanteItem>
...
But. This is why I think generic type constraints should be inherited/enforced on inherited classes just as with member generic type constraints because if we look at definition of MyClass
it says:
MyClass<T> : ClassBase<T>
When T
is DanteItem
we can see that it automatically can't be used with MyClass
because it's inherited from ClassBase<T>
and DanteItem
doesn't fulfill its generic type constraint. I could say that **generic type on MyClass
depends on ClassBase
generic type constraints because otherwise MyClass
could be instantiated with any type. But we know it can't be.
It would be of course different when I would have MyClass
defined as:
public class MyClass<T> : ClassBase<Item>
in this case T doesn't have anything to to with base class' generic type so it's independent from it.
This is all a bit long explanation/reasoning. I could simply sum it up by:
If we don't provide generic type constraint on MyClass
it implicitly implies that we can instantiate MyClass
with any concrete type. But we know that's not possible, since MyClass
is inherited from ClassBase
and that one has a generic type constraint.
I hope this makes much more sense now.
发布评论
评论(4)
另一个更新:
这个问题是我博客的主题2013 年 7 月。感谢您提出的好问题!
更新:
我对此进行了更多思考,我认为问题是您根本不需要继承。相反,您想要的是必须放置在类型参数上的所有约束,以便该类型参数用作另一种类型中的类型参数,以自动推导并以不可见的方式添加到类型参数的声明中. 是吗?
一些简化的示例:
U 是一个类型参数,在必须是 C 的上下文中使用。因此,您认为编译器应该推断出这一点,并自动对 U 施加 C 的约束。
这个怎么样?
现在 V 是在必须同时是 X 和 Y 的上下文中使用的类型参数。因此,您认为编译器应该推断出这一点,并自动对 V 施加 X 和 Y 的约束。是吗?
这又如何呢?
这是我编造的,但我向你保证这是一个完全合法的类型层次结构。请描述一个清晰一致的规则,该规则不会进入无限循环来确定 T、U 和 V 上的所有约束。不要忘记处理已知类型参数是引用类型并且接口约束具有的情况协变或逆变注释!此外,该算法必须具有这样的属性:无论 B、C 和 D 在源代码中出现的顺序如何,它都会给出完全相同相同的结果。
如果约束推断是您想要的功能,那么编译器必须能够处理此类情况,并在无法处理时给出明确的错误消息。
基本类型有什么特别之处?为什么不实际完全实现该功能呢?
V 是在必须可转换为 X 的上下文中使用的类型参数;因此编译器应该推断出这个事实并对 V 施加 X 的约束。是吗?或者没有?
为什么领域很特殊?怎么样:
V 是一个类型参数,在只能是 int 的上下文中使用。因此,C# 编译器应该推断出这一事实并自动对 V 施加 int 约束。
是吗?不?
你知道这是怎么回事吗?它在哪里停止?为了正确实现您想要的功能,编译器必须进行整个程序分析。
编译器不会进行这种级别的分析,因为这是本末倒置。当您构造泛型时,您需要向编译器证明您已满足约束。编译器的工作不是弄清楚你想说什么并找出哪些进一步的约束集满足原始约束。
出于类似的原因,编译器也不会尝试代表您自动推断接口中的方差注释。有关详细信息,请参阅我关于该主题的文章。
http://blogs.msdn.com/b/ericlippert/archive/2007/10/29/covariance-and-contravariance-in-c-part-7-why-do-we-need- a-syntax-at-all.aspx
原答案:
只有成员被继承。约束不是成员。
您只是断言某件事应该如何,而没有提供任何解释为什么它应该是这样。向我们解释一下为什么你相信世界应该是这样的; 好处是什么,缺点是什么,以及成本是什么?
通用约束不会被继承。
默认情况下,功能“未实现”。我们不必提供某个功能未实现的原因! 每个功能只有在有人花钱来实现时才会实现。
现在,我急于指出,泛型类型约束是在方法上继承的。方法是成员,成员是继承的,约束是方法的一部分(尽管不是其签名的一部分)。因此,在继承方法时,约束也随之而来。当您说:
那么
D.M
继承约束并用IEnumerable
替换 T;因此,约束条件是 U 必须可转换为IEnumerable
。请注意,C# 不允许您重新声明约束。我认为这是一个错误。为了清楚起见,我希望能够重申这一限制。但 D 并没有从 B 继承对 T 的任何类型的约束;我不明白这怎么可能。 M 是 B 的成员,并且由 D 及其约束继承。但T本来就不是B的成员,那有什么可继承的呢?
我真的完全不明白你想要什么功能。你能解释得更详细吗?
ANOTHER UPDATE:
This question was the subject of my blog in July 2013. Thanks for the great question!
UPDATE:
I've given this some more thought and I think the problem is that you don't want inheritance at all. Rather, what you want is for all constraints that must be placed on a type parameter in order for that type parameter to be used as a type argument in another type to be automatically deduced and invisibly added to the declaration of the type parameter. Yes?
Some simplified examples:
U is a type parameter that is used in a context where it must be C. Therefore in your opinion the compiler should deduce that and automatically put a constraint of C on U.
What about this?
Now V is a type parameter used in a context where it must be both X and Y. Therefore in your opinion the compiler should deduce that and automatically put a constraint of X and Y on V. Yes?
What about this?
I just made that up, but I assure you that it is a perfectly legal type hierarchy. Please describe a clear and consistent rule that does not go into infinite loops for determining what all the constraints are on T, U and V. Don't forget to handle the cases where type parameters are known to be reference types and the interface constraints have covariance or contravariance annotations! Also, the algorithm must have the property that it gives exactly the same results no matter what order B, C and D appear in source code.
If inference of constraints is the feature you want then the compiler has to be able to handle cases like this and give clear error messages when it cannot.
What is so special about base types? Why not actually implement the feature all the way?
V is a type parameter used in a context where it must be convertible to X; therefore the compiler should deduce this fact and put a constraint of X on V. Yes? Or no?
Why are fields special? What about this:
V is a type parameter used in a context where it can only be int. Therefore the C# compiler should deduce this fact and automatically put a constraint of int on V.
Yes? No?
You see where this is going? Where does it stop? In order to implement your desired feature properly the compiler must do whole-program analysis.
The compiler does not do this level of analysis because that is putting the cart before the horse. When you construct a generic, you are required to prove to the compiler that you've satisfied the constraint. It's not the compiler's job to figure out what you meant to say and work out what further set of constraints satisfy the original constraint.
For similar reasons, the compiler also does not attempt to automatically infer variance annotations in interfaces on your behalf. See my article on that subject for details.
http://blogs.msdn.com/b/ericlippert/archive/2007/10/29/covariance-and-contravariance-in-c-part-seven-why-do-we-need-a-syntax-at-all.aspx
Original answer:
Only members are inherited. A constraint is not a member.
You're just asserting how something should be, without providing any explanation of why it should be that way. Explain to us why you believe that the world should be that way; what are the benefits and what are the drawbacks and what are the costs?
Generic constraints are not inherited.
Features are "not implemented" by default. We don't have to provide a reason why a feature is not implemented! Every feature is not implemented until someone spends the money to implement it.
Now, I hasten to note that generic type constraints are inherited on methods. Methods are members, members are inherited, and the constraint is a part of the method (though not part of its signature). So the constraint comes along with the method when it is inherited. When you say:
Then
D<V>.M<U>
inherits the constraint and substitutesIEnumerable<V>
for T; thus the constraint is that U must be convertible toIEnumerable<V>
. Note that C# does not allow you to restate the constraint. This is in my opinion a misfeature; I would like to be able to restate the constraint for clarity.But D does not inherit any kind of constraint on T from B; I don't understand how it possibly could. M is a member of B, and is inherited by D along with its constraint. But T is not a member of B in the first place, so what is there to inherit?
I'm really not understanding at all what feature it is that you want here. Can you explain with more details?
下面是一种场景,其中此行为的隐式性质会导致与预期不同的行为:
我认识到此场景在设置量方面可能显得过多,但这只是此行为可能导致问题的一个示例。软件应用程序可能很复杂,因此即使这种情况看起来很复杂,我也不认为这种情况不会发生。
在此示例中,有一个 Operator 类实现了两个类似的接口:IMonitor 和 IProcessor。两者都有一个 start 方法和一个 IsStarted 属性,但 Operator 类中每个接口的行为是独立的。即,Operator 类中有一个 _MonitorStarted 变量和一个 _ProcessorStarted 变量。
MyClass
派生自ClassBase
。 ClassBase 对 T 有一个类型约束,它必须实现 IProcessor 接口,并且根据建议的行为 MyClass 继承该类型约束。MyClass
有一个 Check 方法,该方法是在假设可以从内部 IProcessor 对象获取 IProcessor.IsStarted 属性的值的情况下构建的。假设有人更改了 ClassBase 的实现,以删除 IProcessor 对泛型参数 T 的类型约束,并将其替换为 IMonitor 的类型约束。该代码将默默地工作,但会产生不同的行为。原因是
MyClass
中的 Check 方法现在调用 IMonitor.IsStarted 属性而不是 IProcessor.IsStarted 属性,即使MyClass
的代码> 完全没有改变。Below is a scenario where the implicit nature of this behavior causes different behavior than expected:
I recognize that this scenario may seem extravagant in the amount of setup, but this is just one example of where this behavior might cause a problem. Software applications can be complicated, so even though this scenario may seem complicated, I wouldn't say that this can't happen.
In this example there is an Operator class that implements two similar interfaces: IMonitor and IProcessor. Both have a start method and an IsStarted property, but the behavior for each interface within the Operator class is separate. I.e. there is a _MonitorStarted variable and a _ProcessorStarted variable within the Operator class.
MyClass<T>
derives fromClassBase<T>
. ClassBase has a type constraint on T that it must implement the IProcessor interface, and according to the suggested behavior MyClass inherits that type constraint.MyClass<T>
has a Check method, which is built with the assumption that it can get the value of the IProcessor.IsStarted property from the inner IProcessor object.Suppose someone changes the implementation of ClassBase to remove the type constraint of IProcessor on the generic parameter T and replace it with a type contraint of IMonitor. This code will silently work, but will produce different behavior. The reason is because the Check method in
MyClass<T>
is now calling the IMonitor.IsStarted property instead of the IProcessor.IsStarted property, even though the code forMyClass<T>
hasn't changed at all.我认为您很困惑,因为您也在使用
TItem
声明派生类。如果你想一想,如果你使用
Q
来代替的话。那么如何判断
Q
是item
类型呢?您还需要将约束添加到派生类 Generic Type 中,以便
I think you're confused becuase you're declaring you derived class with
TItem
as well.If you think about it if you were using
Q
instead so.Then how is it to be determined that
Q
is of the typeitem
?You need to add the constraint to the derived classes Generic Type as well so
因为 ClassBase 对他的模板有一个约束(应该是 typeof Item),所以你也必须将这个约束添加到 MyClass 中。
如果不这样做,您可以创建 MyClass 的新实例,其中模板不是 Item 类型。创建基类时,会失败。
[编辑]
嗯,现在重新阅读您的问题,我看到您的代码可以编译吗?好的。
好吧,在 MyClass 中,您不知道 this.items 的基类型,因此无法调用 Check 方法。
this.items 是 IList 类型,并且在您的类中,未指定 TItem,这就是该类不理解 Check 方法的原因。
让我反驳你的问题,为什么你不想将约束添加到你的 MyClass 类中?将任何其他类类型作为此类的模板,都会导致错误。为什么不通过添加约束来防止此错误,这样编译时就会失败。
Because the ClassBase has a constraint on his template (should by typeof Item), you have to add this constraint to MyClass too.
If you don't do this, you could create a new instance of MyClass, where the template isn't a type of Item. When creating the base class, it will fail.
[edit]
Hmm now a re-read your question, and I see your code does compile? Ok.
Well, im MyClass you don't know the basetype of this.items, so you can't call the Check method.
this.items is of the type IList, and in your class, TItem isn't specified, thats why the class doesn't understand the Check method.
Let me counter your question, why don't you want to add the constraint to your MyClass class? Given any other class type as template to this class, would result in an error. Why not prevent this errors by adding a constraint so it will fail compiletime.