В ответ на ваш комментарий:
В какой-то момент мне нужен список деревьев. И список Tree<Fruit>
не делает этого, потому что не позволяет мне добавлять Tree<Banana>
экземпляры. Это, конечно, скажет, что не существует неявной ссылки между Tree<Fruit>
и Tree<Banana>
.
Основная проблема заключается в том, что вы хотите иметь коллекцию - список деревьев, - которая (косвенно) содержит похожие объекты разных типов. Для устранения неоднозначности этой коллекции из Tree
(которая также является коллекцией Fruit
), давайте назовем ее Orchard
.
Фруктовый сад - (содержит) -> Дерево - (содержит) -> Фрукты
Если вы сделаете неуниверсальный Tree
, все элементы Orchard
могут относиться к этому типу. Но, как вы заметили, это означает, что вы столкнулись с проблемой того, что деревья небезопасны, и вы можете поместить банан в яблоню. Вам придется решить эту проблему с помощью проверок типов во время выполнения в реализации Tree
.
В качестве альтернативы, вы можете создать общий класс Tree<T> where T : Fruit
, так что вы имеете безопасность типов по отношению к подклассу Fruit
, содержащемуся в дереве. Это означает, что Orchard
будет содержать объекты разных типов, что снова требует проверки типов во время выполнения.
(Вы можете создать Orchard со статическим типом безопасности, объявив отдельный метод доступа для каждого типа:
class Tree { }
class Tree<T> : Tree { }
class Trees : IEnumerable<Tree>
{
Tree<Banana> _bananaTree;
Tree<Apple> _appleTree;
//...etc.
Tree<Banana> GetBananaTree() { return _bananaTree; }
Tree<Apple> GetBananaTree() { return _appleTree; }
//...etc.
public IEnumerator<Tree> GetEnumerator()
{
yield return _bananaTree;
yield return _appleTree;
//...etc.
}
}
Но это, вероятно, не то, что вы хотите, поэтому вам нужно иметь приведение где-то .)
Я предполагаю, что вы бы предпочли бросок в Orchard
, чем в Tree
, и в этом случае я бы предложил что-то подобное для базового подхода:
IDictionary<Type, object> orchard = new Dictionary<Type, object>();
//to retrieve the tree
Tree<Banana> bananaTree = (Tree<Banana>)orchard[typeof(Banana)];
(Конечно, вы можете использовать более конкретный тип, чем object
, например Tree
, или ICollection
, или IEnumerable
.)
Я бы пошел дальше и инкапсулировал эту логику в класс Orchard
, который мог бы обеспечить менее подробный синтаксис, выполнив приведение в методе доступа:
var bananaTree = orchard.GetTree<Banana>();
где:
public class Orchard
{
private IDictionary<Type, object> _trees;
//...
public Tree<T> GetTree<T>()
{
return (Tree<T>)_trees[typeof(T)];
}
}
В конечном счете, это прекрасный пример того, почему параметры сопутствующего и контравариантного типов в интерфейсах должны быть ограничены позициями вывода и ввода соответственно. Для типов, которые используются как в качестве входных, так и выходных данных, вам понадобятся проверки типов во время выполнения.