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
部分。)
写完数百行后,我发现ICollection
与IEnumerable
。
什么是解决方法?
编辑:
如果我在不破坏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());
这意味着您的receiverSet
和senderSet
现在与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
实例包含许多类似的实例,包括更多派生的实例? 为什么不分别使用EdgeBase
, Edge
和EdgeCollection
? 但你更了解你的设计。