The purpose of this is to synchronize two collections, sender-side & receiver-side, containing a graph edge, so that when something happens (remove edge, add edge, etc)
You're messing with type safety basically. Your backing collection is an ICollection<EdgeBase>
(which means you can add any EdgeBase
into it) but what you're passing a very specific type, HashSet<Edge>
. How would you add (or remove) AnotherEdgeBaseDerived
into HashSet<Edge>
? If that is the case then this should be possible:
edge.Add(anotherEdgeBaseDerived); // which is weird, and rightly not compilable
If you perform a cast yourself and pass a separate list then that's compilable. Something like:
HashSet<Edge> receiverSet, senderSet;
var edge = new Edge(receiverSet.Cast<EdgeBase>().ToList(),
senderSet.Cast<EdgeBase>().ToList());
which means your receiverSet
and senderSet
are now out of sync with base list in Edge
. You can either have type safety or sync (same reference), you cant have both.
I worry if there exist no good solution to this, but for a good reason. Either pass HashSet<EdgeBase>
to Edge
constructor (better) or let EdgeBase
collections be ICollection<Edge>
(which seems very odd to do).
Or, the best you can have given the design constraints imo is generic
class EdgeBase<T> where T : EdgeBase<T>
{
}
class Edge : EdgeBase<Edge>
{
public Edge(ICollection<Edge> rCol, ICollection<Edge> sCol) : base(rCol, sCol)
{
}
}
Now you can call as usual:
HashSet<Edge> receiverSet = new HashSet<Edge>(), senderSet = new HashSet<Edge>();
var edge = new Edge(receiverSet, senderSet);
To me the fundamental problem is the fuzzy and smelly design. An EdgeBase
instance holding a lot of similar instances, including more derived ones? Why not EdgeBase
, Edge
and EdgeCollection
separately? But you know your design better.
Eric Lippert said that C# will only support type-safe covariance and contravariance. If you would think of it, making ICollection
covariant is not type-safe.
Let's say you have
ICollection<Dog> dogList = new List<Dog>();
ICollection<Mammal> mammalList = dogList; //illegal but for the sake of showing, do it
mammalList.Add(new Cat());
Your mammalList
(which is actually a dogList
) would now then contain a Cat
.
IEnumerable<T>
is covariant because you cannot Add
to it... you can only read from it -- which, in turn, preserves type-safety.