13

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

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

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 一起使用。

4

2 回答 2

22

Eric Lippert 说 C# 将只支持类型安全的协变和逆变。如果您想到它,使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(实际上是 a dogList)现在将包含 a Cat

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

于 2013-06-11T05:31:56.623 回答
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包含许多类似实例的实例,包括更多派生实例?为什么不EdgeBaseEdge分开EdgeCollection?但是你更了解你的设计。

于 2013-06-12T18:07:29.080 回答