繁体   English   中英

覆盖 IEnumerable<t> 在更多派生的 Class - 任何潜在问题?</t>

[英]Override IEnumerable<T> in More Derived Class - Any Potential Issues?

我有一个抽象的自定义集合类型,我们将调用AnimalCollection来存储Animal对象的集合。 需要存储在动物集合中的动物非常特殊,以至于它们拥有自己的AnimalCollection实现,用于存储特定于该类型集合的附加信息,例如TurtleCollectionGiraffeCollection等。

由于我们在这里都是 collections,我认为AnimalCollection应该实现IEnumerable<Animal>以便用户可以迭代它是有道理的。 然而,在我们更多派生类的情况下,实现IEnumerable<Turtle>IEnumerable<Giraffe>等更有意义。

class Animal {}
class Turtle : Animal {}

abstract class AnimalCollection : IEnumerable<Animal>
{
}

class TurtleCollection : AnimalCollection, IEnumerable<Turtle>
{
}

这样,当有人执行foreach (var turtle in turtleCollection)时, var自动属于Turtle类型。 如果没有实现IEnumerable<Turtle> ,用户将不得不输入foreach (Turtle turtle in turtleCollection) 由于我总是在任何我能输入的地方输入var ,我相信我最终会因为我的turtle变量没有被转换为我期望的类型而绊倒,这意味着我的 API 的用户也可能会为此绊倒!

因此,虽然我知道在 inheritance 树中的两个相邻类型上实现IEnumerable<T>类型可能会导致问题,但我想知道在使用多个IEnumerable<T>和同一 inheritance 中的类型时是否存在隐藏的陷阱等级制度?


编辑:这里最明智的解决方案是拥有AnimalCollection<T>然后定义TurtleCollection: AnimalCollection<Turtle>正如一些现有评论/答案所指出的那样。 然而,我的AnimalCollection是非泛型的原因是我调用AnimalCollection的类型实际上代表了tree中的一个节点 实际上,我有类似的类型

class Node {}
class FooNode : Node {}
class BarNode : Node {}

class NodeCollection : Node {}
class FooNodeCollection : NodeCollection {}
class BarNodeCollection : NodeCollection {}

FooNodeCollection在其构造函数中被限制为只接受FooNode类型的对象。 然后我让访问者访问树的节点

class NodeVisitor
{
    abstract void VisitFoo(FooNode foo);
    abstract void VisitBar(BarNode bar);
    abstract void VisitCollection(NodeCollection collection);
}

就访问者而言, NodeCollection中包含的确切节点类型并不重要。 访问者所要做的就是简单地遍历它的孩子并让他们将自己发送给访问者。

潜在的一种解决方案可能是有两种类型 - 访问者使用的NodeCollection NodeCollection是实际实现IEnumerable<T> NodeCollection<T>但是我也有兴趣知道什么类型的问题可能源于以最初陈述的方式使用多个IEnumerable<T>

我将构建代码以使用组合,而不是 inheritance

class Animal {}
class Turtle : Animal { public Color ShellColor {get;set;} }

abstract class AnimalCollection<T> where T : Animal
{
   public IEnumerable<T> Animals {get; set;} 
}

class TurtleCollection : AnimalCollection<Turtle> {}

(...)
foreach( var t in new TurtleCollection().Animals)
{
    Console.WriteLine(t.ShellColor);
}

(更新)

哦,是的, 为什么不从 List<T> 继承呢? 也总是一本好书。

您链接到的问题的另一个答案还暗示了为什么IEnumerable<T>对同一 inheritance 层次结构中的类型的多个实现会很糟糕。 如果您考虑答案的场景,尝试强制转换为基本类型将使运行时做出未定义的选择,这是您应该引用的答案:避免,避免,避免

除此之外,让我提醒您OfType<T>

foreach (var turtle in collection.OfType<Turtle>())
{
    //turtle is guaranteed to be of type Turtle.
    //If no Turtle objects exist in the collection,
    //the iteration will simply not execute!
}

最后,如果您遵循Federico Dipuma的建议,您发布的代码肯定会更清晰:

只需创建一个 class AnimalCollection<TAnimal>: IEnumerable<TAnimal> where TAnimal: Animal

编辑

根据更新后的问题,我的理解是访问者做了以下事情:

public void VisitCollection(NodeCollection node)
{
    //The problem is that, here, there is no way to know
    //(without pattern-matching or type checks in general)
    //what node type the collection contains. However
    //it may not matter at all, so there should be no problem.
}

无论您在哪里编码都可以访问IEnumerable<T>实现,即无论您可以访问特定的 class 类型而不是基本类型,都会出现您的问题。 VisitCollection方法当然不是这些情况之一,因为在该方法的 scope 中,您只知道(参见)一个IEnumerable<Node>而不是一个IEnumerable<FooNode>或其他。 因此,您在此方法中没有“危险”。

但是,如果您的访问者需要知道 collections 的具体节点类型,您将拥有:

public abstract class Visitor
{
    //As in your case...
    abstract void VisitFooNodeCollection(FooNodeCollection collection);
    abstract void VisitBarNodeCollection(BarNodeCollection collection);
}

在这些情况下,您可能无法使用var (我说“可能”,因为它更像“未定义的行为”),因为FooNodeCollection既是IEnumerable<Node>又是IEnumerable<FooNode> (如果我理解正确的话)。 因此,在这种情况下,每当您键入var时,您可能会遇到您提到的问题,即运行时将不知道将您的 object 转换为什么。

但是,使用继承的(而不是相邻的)层次结构类型为您提供了使用OfType<T>解决不一致问题的选项。 如果运行时得到基类型的枚举器,那么你的对象肯定是基类型的,所以它们都会被枚举。 如果派生类型的枚举器运行,当然你的对象已经被约束(由构造函数),所以你仍然是安全的,它们肯定是那个特定的类型,因此所有的都会被枚举。

简而言之,如果您使用OfType<T>转换为正确的类型,则应该没有问题,而且var将是转换类型 T,因此您将进行编译时分析。 但是,因为我认为您可能对访问者没有问题,并且您只考虑在单节点级别如何处理类型,您可能不会遇到此类问题,因为您将在“集合类型不可知” VisitCollection 不过,在这种情况下,我不明白为什么要让继承类也实现IEnumerable<DerivedType>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM