C# - 有什么方法可以投射通用集合吗?

时间:2012-02-02 16:04:57

标签: c# .net c#-4.0 generics casting

我一直忙于C#4.0泛型,现在我基本上想做这样的事情:

public abstract class GenericTree<T> : Tree
    where T : Fruit
{
    public Tree(IFruitCollection<T> fruits)
        : base(fruits) { }
}

基本Tree类如下所示:

public abstract class Tree
{
    private IFruitCollection<Fruit> fruits;

    public IFruitCollection<Fruit> GetFruits
    {
        get { return fruits; }
    }

    public Tree(IFruitCollection<Fruit> fruits)
    {
        this.fruits = fruits;
    }
}

这是我的第一个问题。 GenericTree的构造函数无法将泛型集合强制转换为水果集合。我还得到了GenericTree的实现:

public class AppleTree : GenericTree<Apple>
{
    public AppleTree()
        : base(new FruitCollection<Apple>) { }
}

这是我的第二个问题。当我使用myAppleTree.GetFruits.Add(...)向AppleTree实例添加水果时,我不仅限于苹果。我被允许添加各种水果。我不想要这个。

我尝试通过将此问题添加到GenericTree来解决该问题:

new public IFruitCollection<T> GetFruits
{
    get { return base.GetFruits as IFruitCollection<T>; }
}

但这也不可能。它总是以null返回null。当我的第一个问题得到解决时,这可能会得到解决。

IFruitCollection界面如下所示:

public interface IFruitCollection<T> : ICollection<T>
    where T : Fruit { ... }

FruitCollection类是Collection类的简单实现。 哦,当然,Apple类扩展了Fruit类。

解决方案是使IFruitCollection界面兼容协方差和逆变。但是我该如何实现这一目标呢? &#34; in&#34;或&#34; out&#34;参数关键字是不可能的,因为ICollection接口不允许它。

非常感谢你的帮助!

2 个答案:

答案 0 :(得分:3)

回应你的评论:

  

在某些时候,我需要一个树木列表。并且Tree<Fruit>列表没有这样做,因为我不允许添加Tree<Banana>个实例。它当然会说Tree<Fruit>Tree<Banana>之间没有隐式引用。

基本问题是你想要一个集合 - 树的列表 - (间接地)包含不同类型的类似对象。为了从Tree(也是Fruit的集合)消除该集合的歧义,我们称之为Orchard

  

Orchard - (包含) - &gt;树 - (包含) - &gt;水果

如果您制作非通用Tree,则Orchard的元素都可以是该类型。但正如您所注意到的,这意味着您最终会遇到树木不安全的问题,您可以将香蕉放在苹果树上。您必须在Tree实现中使用运行时类型检查来解决该问题。

或者,您可以创建一个通用的Tree<T> where T : Fruit类,因此您对树中包含的Fruit的子类具有类型安全性。这意味着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更具体的类型,例如TreeICollectionIEnumerable。)

我会更进一步,将该逻辑封装在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)];
    }
}

最终,这是一个很好的例子,说明为什么接口中的共变和反变类型参数必须分别限制在输出和输入位置。对于既用作输入又用作输出的类型,最终需要运行时类型检查。

答案 1 :(得分:1)

我想我找到了解决方案,而我正在尝试phoog的解决方法。谢谢,phoog!

起初我没有给GenericTree他自己的FruitCollection。 Mosty因为基类经常遍历他自己的集合,例如更新水果。这导致水果没有更新,因为它们被添加到GenericTree的集合中,而不是添加到基础集合中。

但最终我意识到,投射这些收藏品也永远无法发挥作用。

现在我已经创建了另一个FruitCollection类,它自动添加和删除基础FruitCollection的组件,反之亦然。这个解决方案对我很有用!

public FruitCollection<T, U> : FruitCollection<T>
    where T : U
    where U : Fruit
{
    private FruitCollection<U> baseCollection;

    public FruitCollection(FruitCollection<U> baseCollection)
        : base()
    {
        this.baseCollection = baseCollection;

        // here I added code that throws events whenever the collection is changed
        // I used those events to add/remove fruit to the base collection, and vice versa
        // see the link below.
        ...
    }

    ...
}

public class GenericTree<T>: Tree
    where T : Fruit
{
    private FruitCollection<T> fruits;

    // use the new keyword to hide the base collection
    new public FruiCollection<T> Fruits
    {
        get { return fruits; }
    }

    public GenericTree()
        : base()
    {
        // after hiding the base collection, use base.Fruits to get it
        fruits = new FruitCollection<T, Fruit>(base.Fruits);
    }
}

这帮助了我:How to handle add to list event?