C# 模板参数中的“协方差”
假设我有这两门课C# 模板参数中的“协方差”,c#,C#,假设我有这两门课 class BaseClass { protected HashSet<BaseClass> container; } class DerivedClass : BaseClass { DerivedClass() { container = new HashSet<DerivedClass>(); } } 然后我收到一个错误:无法转换 因为每个
class BaseClass
{
protected HashSet<BaseClass> container;
}
class DerivedClass : BaseClass
{
DerivedClass()
{
container = new HashSet<DerivedClass>();
}
}
然后我收到一个错误:无法转换
因为每个DerivedClass都应该是基类,所以我不太清楚为什么会抛出这个错误,但事实确实如此
基类的目标是在容器上执行各种操作,只有特定的行为绑定到DerivedClass,其中要求容器的类型为HashSet
这个目标通常是如何实现的?您可以做的是:
class BaseClass<T> where T : BaseClass<T>
{
protected HashSet<T> container;
}
class DerivedClass : BaseClass<DerivedClass>
{
DerivedClass()
{
container = new HashSet<DerivedClass>();
}
}
你能做的是:
class BaseClass<T> where T : BaseClass<T>
{
protected HashSet<T> container;
}
class DerivedClass : BaseClass<DerivedClass>
{
DerivedClass()
{
container = new HashSet<DerivedClass>();
}
}
每个DevrivedClass都是基类,但不是相反。哈希集不能是协变的,因为它允许写操作。因此,在您的场景中,这是可能的:
class BaseClass
{
protected HashSet<BaseClass> container;
public DoSomething()
{
container.Add(new BaseClass()); // not legal if container is really a List<DerivedClass>
}
}
这看起来有点奇怪,但是对于一个类来说,包含相同类型的对象列表看起来很奇怪,所以在您的实际场景中,这可能是有意义的。每个DevrivedClass都是基类,但反过来说就不一样了。哈希集不能是协变的,因为它允许写操作。因此,在您的场景中,这是可能的:
class BaseClass
{
protected HashSet<BaseClass> container;
public DoSomething()
{
container.Add(new BaseClass()); // not legal if container is really a List<DerivedClass>
}
}
这看起来有点奇怪,但是对于一个类来说,包含相同类型的对象列表看起来很奇怪,所以在您的实际场景中,它可能是有意义的。协方差只支持接口。也就是说,容器必须声明为继承IEnumerable的,IEnumerable是协变的 编辑
正如尼科所说,这不会像预期的那样起作用。要阐明为什么这是不可能的,请考虑:HashSet.AddDerivedClassA是一个有效的操作,但HashSet.AddDerivedClassA不是。因此,HashSet不能是HashSet。协方差仅支持接口。也就是说,容器必须声明为继承IEnumerable的,IEnumerable是协变的 编辑 正如尼科所说,这不会像预期的那样起作用。要阐明为什么这是不可能的,请考虑:HashSet.AddDerivedClassA是一个有效的操作,但HashSet.AddDerivedClassA不是。因此,HashSet不能是HashSet 再扩展一点:基类有许多派生类; 派生类在功能上有很多共性。但是 关键的是,每个派生类的变体只需要1个 信息没有冗余。因此,问题是——如何保持干燥 可能的请注意,本例中的基类将具有 对集装箱进行操作。可能会有一个 FlyWeightDerivedClassFactory在新的 DerivedClass被实例化来解决这个问题,但是 去封装事物 考虑创建一个泛型容器类,该类为基类调用其操作提供接口:
interface IContainer {
void Operation1();
void Operation2();
}
class Container<T> : IContainer where T : BaseClass {
private HashSet<T> _internalContainer;
...
}
class BaseClass {
protected IContainer container;
}
class DerivedClass : BaseClass {
DerivedClass() {
container = new Container<DerivedClass>();
}
}
所有特定于类型的操作都应该是容器内部的,或者是从容器派生的类(如果需要的话),而接口方法是类型无关的
再扩展一点:基类有许多派生类;
派生类在功能上有很多共性。但是
关键的是,每个派生类的变体只需要1个
信息没有冗余。因此,问题是——如何保持干燥
可能的请注意,本例中的基类将具有
对集装箱进行操作。可能会有一个
FlyWeightDerivedClassFactory在新的
DerivedClass被实例化来解决这个问题,但是
去封装事物
考虑创建一个泛型容器类,该类为基类调用其操作提供接口:
interface IContainer {
void Operation1();
void Operation2();
}
class Container<T> : IContainer where T : BaseClass {
private HashSet<T> _internalContainer;
...
}
class BaseClass {
protected IContainer container;
}
class DerivedClass : BaseClass {
DerivedClass() {
container = new Container<DerivedClass>();
}
}
所有特定于类型的操作都应该是容器内部的,或者是从容器派生的类(如果需要的话),而接口方法是类型无关的。协方差只允许在接口中使用。我建议阅读整个系列。这听起来不像是一个伟大的架构。为什么基类会和它的派生类型混在一起?为什么集合不能是新的HashSet?您的设计不是类型安全的。如果基类中的某个方法试图将基类的任意实例添加到容器中,则会出现类型冲突。@NicoSchertler:每个派生类都必须将容器专用化。这是一个相当混乱的问题。好吧,接受。我仍然会考虑基类中集合的重要性。可能一个IEnumerable就足够了,它是协变的。协变只允许在接口中出现。我建议阅读整个系列。这听起来不像是一个伟大的架构。为什么基类会和它的派生类型混在一起?为什么集合不能是新的HashSet?您的设计不是类型安全的。如果基类中的某个方法试图将基类的任意实例添加到容器中,则会出现类型冲突。@NicoSchertler:每个派生类都必须将容器专用化。这是一个相当混乱的问题。好吧,接受。我仍然会考虑基类中集合的重要性。也许一个IEnumerable就足够了,这就是covari
ant.IEnumerable是协变的,ISet不是因为它允许读写操作IEnumerable是协变的,ISet并不是因为它允许读写操作。这种关系似乎是一个很好的例子,说明什么时候可能有更好的设计不使用继承。这种关系似乎是一个很好的例子,说明什么时候可能有更好的设计不使用继承。