为什么通用类型约束不可inheritance/分层实施
物品类
public class Item { public bool Check(int value) { ... } }
具有generics类型约束的基本抽象类
public abstract class ClassBase where TItem : Item { protected IList items; public ClassBase(IEnumerable items) { this.items = items.ToList(); } public abstract bool CheckAll(int value); }
没有约束的inheritance类
public class MyClass : ClassBase { 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; } }
我想知道为什么generics类型约束不可inheritance? 因为如果我的inheritance类inheritance自基类并传递其对基类具有约束的generics类型,则它自动意味着inheritance类中的generics类型应具有相同的约束而不显式定义它。 不应该吗?
我做错了什么,理解它是错的还是通用类型约束真的不可inheritance? 如果后者是真的, 为什么世界就是这样呢?
一点额外的解释
为什么我认为在类上定义的generics类型约束应该在子类上inheritance或强制执行? 让我给你一些额外的代码,使它不那么明显。
假设我们按上述方法拥有所有三个类。 然后我们也有这个类:
public class DanteItem { public string ConvertHellLevel(int value) { ... } }
正如我们所看到的,这个类不会inheritance自Item
因此它不能用作ClassBase
的具体类(忘记ClassBase
现在是抽象的事实。它也可以是常规类)。 由于MyClass
没有为其generics类型定义任何约束,因此使用MyClass
似乎完全有效…
但。 这就是为什么我认为generics类型约束应该inheritance/强制执行inheritance类,就像成员generics类型约束一样,因为如果我们看一下MyClass
定义,它会说:
MyClass : ClassBase
当T
是DanteItem
我们可以看到它自动不能与MyClass
一起使用,因为它inheritance自ClassBase
并且DanteItem
其generics类型约束。 我可以说MyClass
上的**generics类型依赖于ClassBase
generics类型约束,因为否则MyClass
可以用任何类型实例化。 但我们知道它不可能。
当我将MyClass
定义为:当然会有所不同:
public class MyClass : ClassBase
在这种情况下,T与基类’generics类型没有任何关系,因此它独立于它。
这是一个有点长的解释/推理。 我可以简单地总结一下:
如果我们不在
MyClass
上提供generics类型约束,它隐含地暗示我们可以用任何具体类型实例化MyClass
。 但我们知道这是不可能的,因为MyClass
是从ClassBase
inheritance的,并且它具有generics类型约束。
我希望现在这更有意义。
另一个更新:
这个问题是我2013年7月的博客主题 。 谢谢你这个好问题!
更新:
我已经给了这个更多的想法,我认为问题在于你根本不需要inheritance 。 相反,您想要的是必须放置在类型参数上的所有约束,以便将该类型参数用作另一种类型的类型参数,以便自动推导并无形地添加到类型参数的声明中。 是?
一些简化的例子:
class B where T:C {} class D : B {}
U是一个类型参数,在必须为C的上下文中使用。因此在您看来,编译器应该推导出它并自动在U上设置C的约束。
那这个呢?
class B where T : X where U : Y {} class D : B {}
现在V是一个在上下文中使用的类型参数,它必须是X和Y.因此在您看来,编译器应该推导出并自动将X和Y的约束放在V上。是吗?
那这个呢?
class B where T : C {} class C : B> where U : IY> {} class D : C> where V : IZ {}
我刚刚做到了,但我向你保证,这是一个完全合法的类型层次结构。 请描述一个清晰且一致的规则,该规则不会进入无限循环以确定T,U和V上的所有约束。不要忘记处理类型参数已知为引用类型且接口约束具有的情况协方差或逆变注释! 此外,无论B,C和D在源代码中出现什么顺序,算法都必须具有完全相同的结果。
如果推理约束是你想要的function,那么编译器必须能够处理这样的情况并在不能的情况下给出明确的错误消息。
基类型有什么特别之处? 为什么不一直实现该function呢?
class B where T : X {} class D { B bv; }
V是在必须可转换为X的上下文中使用的类型参数; 因此编译器应该推导出这个事实,并在X上设置X的约束。是吗? 或没有?
为什么字段特殊? 那这个呢:
class B { static public void M(ref U u) where U : T {} } class D : B { static V v; static public void Q() { M(ref v); } }
V是在上下文中使用的类型参数,它只能是int。 因此,C#编译器应该推导出这个事实并自动在V上设置int的约束。
是? 没有?
你看到这是怎么回事? 它停在哪里? 为了正确实现所需的function,编译器必须进行全程序分析。
编译器没有进行这种级别的分析,因为这会将购物车放在马前。 构造generics时,需要向编译器certificate您已满足约束。 编译器的工作不是弄清楚你想要说什么,而是找出进一步的约束条件满足原始约束条件。
出于类似的原因,编译器也不会尝试代表您自动推断接口中的方差注释。 有关详细信息,请参阅有关该主题的文章
原始答案:
我想知道为什么generics类型约束不可inheritance?
只有成员才能inheritance。 约束不是成员。
如果我的inheritance类inheritance自基类并传递其对基类具有约束的generics类型,则它自动意味着inheritance类中的generics类型应该具有相同的约束而不显式定义它。 不应该吗?
你只是断言应该是什么,而不提供任何解释为什么它应该是那样的。 向我们解释为什么你认为世界应该是这样的; 有什么好处 ,有什么缺点 , 成本是多少?
我做错了什么,理解它是错的还是通用类型约束真的不可inheritance?
通用约束不会被inheritance。
如果后者是真的,为什么世界就是这样呢?
默认情况下,function“未实现”。 我们不必提供未实现function的原因! 除非有人花钱实施,否则每项function都不会实施。
现在,我必须注意generics类型约束是在方法 上inheritance的。 方法是成员,成员是inheritance的,约束是方法的一部分(虽然不是其签名的一部分)。 因此约束随着inheritance的方法而出现。 当你说:
class B { public virtual void M() where U : T {} } class D : B> { public override void M() {} }
然后D
inheritance约束并用IEnumerable
代替T; 因此约束是U必须可转换为IEnumerable
。 请注意,C#不允许您重新声明约束。 在我看来,这是一种错误; 为了清晰起见,我希望能够重申约束。
但D并没有从Binheritance对T的任何约束; 我不明白它怎么可能。 M是B的成员 ,由D及其约束inheritance。 但是T首先不是B的成员,那么inheritance什么呢?
我真的不明白你想要的function是什么。 你能解释一下更多细节吗?
我觉得你很困惑,因为你宣布你也是TItem
派生类。
如果您考虑使用Q
而不是这样的话。
public class MyClass : BaseClass { ... }
那么如何确定Q
属于类型item
呢?
您需要将约束添加到派生类Generic Type中
public class MyClass : BaseClass were Q : Item { ... }
下面是这种行为的隐式性质导致不同于预期的行为的情况:
我认识到这种情况在设置量上可能看起来过于奢侈,但这只是这种行为可能导致问题的一个例子。 软件应用程序可能很复杂,因此即使这种情况看起来很复杂,我也不会说这不会发生。
在此示例中,有一个Operator类实现了两个类似的接口:IMonitor和IProcessor。 两者都有一个start方法和一个IsStarted属性,但Operator类中每个接口的行为是分开的。 即在Operator类中有一个_MonitorStarted变量和一个_ProcessorStarted变量。
MyClass
派生自ClassBase
。 ClassBase对T有一个类型约束,它必须实现IProcessor接口,并根据建议的行为,MyClassinheritance该类型约束。
MyClass
有一个Check方法,它假设它可以从内部IProcessor对象获取IProcessor.IsStarted属性的值。
假设某人更改了ClassBase的实现,以删除generics参数T上的IProcessor类型约束,并将其替换为IMonitor的类型约束。 此代码将默默工作,但会产生不同的行为。 原因是因为MyClass
的Check方法现在调用IMonitor.IsStarted属性而不是IProcessor.IsStarted属性,即使MyClass
的代码根本没有改变。
public interface IMonitor { void Start(); bool IsStarted { get; } } public interface IProcessor { void Start(); bool IsStarted { get; } } public class Operator : IMonitor, IProcessor { #region IMonitor Members bool _MonitorStarted; void IMonitor.Start() { Console.WriteLine("IMonitor.Start"); _MonitorStarted = true; } bool IMonitor.IsStarted { get { return _MonitorStarted; } } #endregion #region IProcessor Members bool _ProcessorStarted; void IProcessor.Start() { Console.WriteLine("IProcessor.Start"); _ProcessorStarted = true; } bool IProcessor.IsStarted { get { return _ProcessorStarted; } } #endregion } public class ClassBase where T : IProcessor { protected T Inner { get; private set; } public ClassBase(T inner) { this.Inner = inner; } public void Start() { this.Inner.Start(); } } public class MyClass : ClassBase //where T : IProcessor { public MyClass(T inner) : base(inner) { } public bool Check() { // this code was written assuming that it is calling IProcessor.IsStarted return this.Inner.IsStarted; } } public static class Extensions { public static void StartMonitoring(this IMonitor monitor) { monitor.Start(); } public static void StartProcessing(this IProcessor processor) { processor.Start(); } } class Program { static void Main(string[] args) { var @operator = new Operator(); @operator.StartMonitoring(); var myClass = new MyClass(@operator); var result = myClass.Check(); // the value of result will be false if the type constraint on T in ClassBase is where T : IProcessor // the value of result will be true if the type constraint on T in ClassBase is where T : IMonitor } }
因为ClassBase对他的模板有一个约束(应该通过typeof Item),你也必须将这个约束添加到MyClass中。 如果不这样做,可以创建MyClass的新实例,其中模板不是Item的类型。 创建基类时,它将失败。
[编辑]嗯现在重新阅读你的问题,我看到你的代码确实编译了? 好。
好吧,我的MyClass你不知道this.items的基本类型,所以你不能调用Check方法。 this.items属于IList类型,并且在您的类中,未指定TItem,这就是为什么该类不理解Check方法。
让我反驳你的问题,为什么你不想将约束添加到你的MyClass类? 给定此类的任何其他类类型作为模板,将导致错误。 为什么不通过添加约束来防止此错误,因此它将使编译时失败。