ICollection 不协变?

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

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

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

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

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

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

写完数百行后,我发现ICollectionIEnumerable

什么是解决方法?

编辑:

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

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

但这是非法的; ‘U’不能与forms参数T一起使用。

Eric Lippert说C#只支持类型安全的协方差和逆变。 如果你想到它,使ICollection协变不是类型安全的。

让我们说你有

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

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

IEnumerable是协变的,因为你无法Add它…你只能从中读取 – 这反过来又保留了类型安全性。

你基本上搞乱了类型安全。 您的支持集合是ICollection (这意味着您可以将任何EdgeBase添加到其中)但是您传递的是非常特定的类型HashSet 。 如何将AnotherEdgeBaseDerived添加(或删除)到HashSet ? 如果是这种情况那么这应该是可能的:

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

如果你自己进行演员并传递一个单独的列表,那么这是可编辑的。 就像是:

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

这意味着您的receiverSetsenderSet现在与Edge基本列表不同步。 你可以有类型安全或同步(相同的参考), 你不能两者兼得。

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

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

 class EdgeBase where T : EdgeBase { } class Edge : EdgeBase { public Edge(ICollection rCol, ICollection sCol) : base(rCol, sCol) { } } 

现在你可以照常打电话:

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

对我来说,根本问题是模糊和有臭味的设计。 EdgeBase实例包含许多类似的实例,包括更多派生的实例? 为什么不分别使用EdgeBaseEdgeEdgeCollection ? 但你更了解你的设计。