【问题标题】:C# - Is there some way to cast a generic collection?C# - 有什么方法可以转换通用集合吗?
【发布时间】:2012-02-02 16:04:57
【问题描述】:

我一直忙于 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。当我的第一个问题解决后,这可能会得到解决。

IFruitCollection 界面如下所示:

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

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

解决方案是使 IFruitCollection 接口同时兼容协变和逆变。但是我该如何实现呢? "in" 或 "out" 参数关键字是不可能的,因为 ICollection 接口不允许这样做。

非常感谢您的帮助!

【问题讨论】:

  • 请注意,这可以通过将 Tree 和 GenericTree 类合并为一个来轻松解决。但请记住,这只是我的问题的一个例子。我自己的应用程序稍微复杂一些。
  • 正如您所暗示的,解决方案是使 Tree 通用。您还暗示这是不可能的。为什么不呢?
  • 在某些时候,我需要一个树列表。并且 Tree 的列表不会这样做,因为不允许我添加 Tree 实例。它当然会说 Tree 和 Tree 之间没有隐式引用。

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


【解决方案1】:

回应您的评论:

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

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

果园 -(包含)-> 树 -(包含)-> 水果

如果您创建一个非泛型TreeOrchard 的元素都可能属于该类型。但正如您所注意到的,这意味着您最终会遇到树木类型不安全的问题,您可以将香蕉放在苹果树上。您必须通过 Tree 实现中的运行时类型检查来解决该问题。

或者,您可以创建一个通用的Tree&lt;T&gt; 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 更具体的类型,例如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)];
    }
}

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

【讨论】:

  • 感谢您的回复。昨天我意识到我需要摆脱非泛型 Tree 类,你实际上为我证实了这一点。您的 Orchard 课程似乎是一个很好的解决方法。恐怕这不适用于我的“真实”应用程序,但我明天会尝试一下。
  • 您的解决方法确实对我有用,但我在使用您的解决方法时发现了另一种解决方法。看看我自己的答案。不过感谢您的帮助!
  • @Rudey 这是一个有趣的解决方案,如果您没有大量对象,它会很好地工作。我有一个项目,其中集合包含数百万个对象,并且为不同类型的引用提供 两个 集合的存储开销在那里是不可接受的。
  • @Rudey 三思而后行,我不明白这会给你带来什么。您存储这些对象的列表是什么类型的?是List&lt;Tree&gt;吗?如果是这样,您仍然会遇到这样的问题,即当您从该列表中检索GenericTree&lt;Apple&gt; 时,引用的类型为Tree,编译器无法阻止您向其中添加Banana。您仍然需要运行时类型检查。
  • 你是对的。但是我的 List 不用于添加水果,所以它是哪种果树并不重要。在我的情况下,水果只会通过 this.Fruits.Add(...) 添加到该树的构造函数中。如果我打算使用该列表来添加水果,我肯定会使用您的解决方案。我想下次将我的问题转化为简单案例时会更加小心。
【解决方案2】:

我想我在尝试 phoog 的解决方法时找到了我的解决方案。谢谢,笨蛋!

起初我没有给 GenericTree 他自己的 FruitCollection。大多数是因为基类经常循环遍历他自己的集合,例如更新水果。这导致水果没有更新,因为它们被添加到 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?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-20
    • 2015-08-04
    • 1970-01-01
    • 2020-01-10
    • 2018-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多