这样做的目的是同步两个集合,发送方和集合。接收方,包含图形边缘,以便在发生某些事情时(删除边缘,添加边缘等)通知双方。
为此,对集合的(反向)引用包含在集合
中的元素中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一起使用。
答案 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());
表示您的receiverSet
和senderSet
现在与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
实例包含许多类似的实例,包括更多派生的实例?为什么不分别EdgeBase
,Edge
和EdgeCollection
?但你更了解你的设计。