ICollection <T> не ковариант?
Целью этого является синхронизация двух коллекций, стороне отправителя и получателя, содержащей край графика, так что когда что-то происходит (удаляем край, добавляем кромку и т.д.), обе стороны уведомляются.
Чтобы сделать это, обратные ссылки на коллекции были включены в элемент в коллекциях
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;
}
Deletion (Disconnect) было нормально, но проблема возникла во время создания:
HashSet<Edge> receiverSet, senderSet;
var edge = new Edge(receiverSet, senderSet); // Can't convert Edge to EdgeBase!
Хотя Edge
получен из EdgeBase
, это незаконно.
(Проблема состоит в Edge
part, not HashSet<>
part.)
После написания сотен строк, которые я обнаружил, ICollection<>
не является ковариантным, как IEnumerable<>
.
Что может быть обходным способом?
EDIT:
Если бы я написал код выше, не нарушая правила ковариации С#, это было бы так:
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.
Ответы
Ответ 1
Эрик Липперт сказал, что С# будет поддерживать только ковариацию и контравариантность по типу. Если вы думаете об этом, создание ICollection
covariant не безопасно для типов.
Скажем, у вас есть
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
к нему... вы можете читать только его, что, в свою очередь, сохраняет безопасность типов.
Ответ 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
отдельно? Но вы знаете, что ваш дизайн лучше.