ICollection <t>不是Covariant?</t>

时间:2013-06-08 06:07:06

标签: c# generics inheritance covariance

这样做的目的是同步两个集合,发送方和集合。接收方,包含图形边缘,以便在发生某些事情时(删除边缘,添加边缘等)通知双方。

为此,对集合的(反向)引用包含在集合

中的元素中
class EdgeBase {
    EdgeBase(ICollection<EdgeBase> rCol, ICollection<EdgeBase> sCol)     
    { RecvCol=rCol;  SendCol=sCol; }      
    ICollection<EdgeBase> RecvCol;      
    ICollection<EdgeBase> SendCol;       
    public virtual void Disconnect() // Synchronized deletion         
    { RecvCol.Remove(this);  SendCol.Remove(this); }                 
}         
class Edge : EdgeBase {       
    Edge(ICollection<EdgeBase> rCol, ICollection<EdgeBase> sCol)     
    : base(rCol, sCol) {}
    int Weight;     
}      

删除(断开连接)没问题,但在创建过程中出现问题:

HashSet<Edge> receiverSet, senderSet;
var edge = new Edge(receiverSet, senderSet); // Can't convert Edge to EdgeBase!

虽然Edge来自EdgeBase,但这是非法的。 (问题是Edge部分,而不是HashSet<>部分。)

写完数百行后,我发现ICollection<>IEnumerable<>不一致。

什么是解决方法?

编辑:

如果我在不违反C#的协方差规则的情况下编写上面的代码,那就是这样的:

public class EdgeBase<T, U>
    where T : ICollection<U<T>> // illegal
    where U : EdgeBase<T, U>    // legal, but introduces self-reference
{
    public EdgeBase(T recvCol, T sendCol) {...}
    protected T ReceiverCollection;
    protected T SenderCollection;
    public virtual void Disconnect() {...}
}

但这是非法的; 'U'不能与形式参数T一起使用。

2 个答案:

答案 0 :(得分:16)

Eric Lippert said that C# will only support type-safe covariance and contravariance.如果你想到这一点,那么使ICollection协变不是类型安全的。

假设你有

ICollection<Dog> dogList = new List<Dog>();
ICollection<Mammal> mammalList = dogList; //illegal but for the sake of showing, do it
mammalList.Add(new Cat());

您的mammalList(实际上是dogList)现在将包含Cat

IEnumerable<T>是协变的,因为你不能Add它...你只能从中读取 - 这反过来又保留了类型安全。

答案 1 :(得分:2)

你基本上在搞乱类型安全。您的支持集合是ICollection<EdgeBase>(这意味着您可以在其中添加任何EdgeBase),但是您传递的是非常具体的类型HashSet<Edge>。您如何在AnotherEdgeBaseDerived中添加(或移除)HashSet<Edge>?如果是这种情况,那么这应该是可能的:

edge.Add(anotherEdgeBaseDerived); // which is weird, and rightly not compilable

如果您自己进行演员表并通过单独的列表,那么这是可编辑的。类似的东西:

HashSet<Edge> receiverSet, senderSet;
var edge = new Edge(receiverSet.Cast<EdgeBase>().ToList(), 
                    senderSet.Cast<EdgeBase>().ToList()); 

表示您的receiverSetsenderSet现在与Edge中的基本列表不同步。你可以有类型安全或同步(相同参考),你不能同时拥有。

我担心如果没有好的解决办法,但有充分的理由。要么将HashSet<EdgeBase>传递给Edge构造函数(更好),要么让EdgeBase个集合为ICollection<Edge>(这看起来很奇怪)。

或者,给定设计约束imo的最佳效果是通用的

class EdgeBase<T> where T : EdgeBase<T>
{

}

class Edge : EdgeBase<Edge>
{
    public Edge(ICollection<Edge> rCol, ICollection<Edge> sCol) : base(rCol, sCol)
    {

    }
}

现在你可以像往常一样打电话:

HashSet<Edge> receiverSet = new HashSet<Edge>(), senderSet = new HashSet<Edge>();
var edge = new Edge(receiverSet, senderSet);

对我而言,根本问题在于模糊和臭味的设计。一个EdgeBase实例包含许多类似的实例,包括更多派生的实例?为什么不分别EdgeBaseEdgeEdgeCollection?但你更了解你的设计。