无法在派生 class 的 IEnumerable 基础 class 上使用 LINQ 方法

Cannot use LINQ methods on IEnumerable base class from derived class

我正在尝试在 class 中实现 IEnumerable<Turtle>,派生自已实现 IEnumerable<Animal> 的基础 class。

为什么在 class Turtle 的任何方法中调用 base.Cast<Turtle>()(或基本元素上的任何 LINQ 方法)都无法编译?

无法将 base 替换为 this,因为它显然会导致 WhosebugException

这里是复制问题的最小代码示例:

public interface IAnimal {}

public class Animal : IAnimal {}

public class Turtle : Animal {}

public class AnimalEnumerable : IEnumerable<Animal> {
    List<Animal> Animals = new List<Animal>();
    IEnumerator<Animal> IEnumerable<Animal>.GetEnumerator() {
        return Animals.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return Animals.GetEnumerator();
    }
}

public class TurtleEnumerable : AnimalEnumerable, IEnumerable<Turtle> {
    IEnumerator<Turtle> IEnumerable<Turtle>.GetEnumerator() {
        return base.Cast<Turtle>().GetEnumerator(); //FAILS WITH "CANNOT RESOLVE SYMBOL Cast"
    }
}

出于某种原因,将 base.Cast<Turtle>().GetEnumerator(); 替换为 this.OfType<Animal>().Cast<Turtle>().GetEnumerator(); 可以在不抛出 WhosebugException 的情况下工作,但我不知道为什么。

我会回答这个问题:

Why will calling base.Cast() (or any LINQ method on the base element) in any method from the class Turtle fail to compile?

该异常的原因是 Cast,其他此类方法是 扩展方法 。扩展方法是静态的。

例如,让我们看一下:

public static class Extensions
{
    public static void Method2(this Base b) ...
}

public class Base 
{
    public void Method1() ...
}

public class Derived:Base
{
    public void Test()
    {
        base.Method1();

        base.Method2(); // Does not contain a definition
    }
}

如您所知,扩展方法是一种非常好的语法糖。它们并没有真正添加到 class 中,但编译器让它感觉像是。因此,编译器会将那行代码更改为那一行:

 Extensions.Method2(base); 

如果你用那个代码替换你的代码,编译器会给出更合适的错误消息:关键字base的使用在此上下文中无效。

MSDN所述:

A base class access is permitted only in a constructor, an instance method, or an instance property accessor.

Eric Lippert 之前曾表示这是一个有意识的设计决定 here。在您首先可以访问基础 class 的实现的情况下,您从来没有打算使用扩展方法。

而且如果你仔细想想,你也不需要在这里做这些。创建一个 GetEnumerator 属性 或受保护的方法并使用它!基本面向对象;不用在这里折磨linq了。

编辑: 有人指出我之前的建议没有用。所以让我建议不要实现两个不同的 IEnumerable 接口,因为无论如何这都会给 foreach 带来很多麻烦。

我开始相信这个实现可能是您真正想要的:

public interface IAnimal { }

public class Animal : IAnimal { }

public class Turtle : Animal { }

public class AnimalEnumerable : IEnumerable<Animal>
{
    IEnumerator<Animal> IEnumerable<Animal>.GetEnumerator()
    {
        throw new NotImplementedException();
    }


    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

public class TurtleEnumerable : AnimalEnumerable
{
}

然后您可以枚举 Animal 及其派生词等

为什么要在 TurtleEnumerator class 上实现 IEnumerable?另外,我不认为实现 IEnumerable 接口时 AnimalEnumerable 的可访问性是正确的。

难道不是这样实现的吗:

    public interface IAnimal { }

    public class Animal : IAnimal { }

    public class Turtle : Animal { }

    public class AnimalEnumerable : IEnumerable<Animal>
    {
        protected List<Animal> Animals = new List<Animal>();

        public IEnumerator<Animal> GetEnumerator()
        {
            return Animals.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return Animals.GetEnumerator();
        }
    }

    public class TurtleEnumerable : AnimalEnumerable
    {
        public void AddTurtle(Turtle turtle)
        {
            Animals.Add(turtle);
        }

        public IEnumerable<Turtle> GetTurtles()
        {
            var iterator = GetEnumerator();

            yield return iterator.Current as Turtle;
        }
    }

    [Test]
    public void CanAddTurtles()
    {
        Turtle one = new Turtle();
        Turtle two = new Turtle();

        TurtleEnumerable turtleStore = new TurtleEnumerable();

        turtleStore.AddTurtle(one);
        turtleStore.AddTurtle(two);

        foreach (var turtle in turtleStore.GetTurtles())
        {
            // Do something with the turtles....
        }
    }

您的方法存在几个问题。 TurtleEnumerable 实现了 IEnumerable<Animal>IEnumerable<Turtle>。为了能够在 foreach 循环中使用 TurtleEnumerable 实例,您必须强制转换它以便代码编译:

foreach (var turtle in (IEnumerable<Turtle>) turtleEnumerable)

您还使用显式接口实现来隐藏通用 GetEnumerator() 方法。您必须这样做,因为您不能单独对 return 类型进行重载解析,并且两个通用 GetEnumerator() 方法仅在 return 类型上有所不同。

但是,这意味着 TurtleEnumerable 方法不能调用基础 GetEnumerator() 方法。这样做的原因是 base 的行为不像 "base" 类型的变量。相反,它是一个保留字,只能用于调用基本 class 方法。一个推论是扩展方法不能与 base 一起使用。此外,您不能强制转换 base,因此无法通过 base.

调用基于 class 的显式接口实现

但是,您可以投射 this,但是因为 TurtleEnumerable 上的通用 GetEnumerator() 隐藏了 AnimalEnumerable 上的通用 GetEnumerator(),您将无法调用基础 class 所以你会得到堆栈溢出,因为在某些时候 TurtleEnumerable.GetEnumerator() 的实现将调用相同的 GetEnumerator.

为了编译你的代码,你需要在你的基础 class 中创建一个 protected IEnumerator<Animal> GetEnumerator() 方法,并创建你自己的 TurtleEnumerator class 来包装你的基础枚举器实例可以通过调用protected方法获取。

public class TurtleEnumerable : AnimalEnumerable, IEnumerable<Turtle> {

  IEnumerator<Turtle> IEnumerable<Turtle>.GetEnumerator() {
    return new TurtleEnumerator(base.GetEnumerator());
  }

  sealed class TurtleEnumerator : IEnumerator<Turtle> {

    IEnumerator<Animal> animalEnumerator;

    public TurtleEnumerator(IEnumerator<Animal> animalEnumerator) {
      this.animalEnumerator = animalEnumerator;
    }

    public Turtle Current {
      get { return (Turtle) animalEnumerator.Current; }
    }

    Object IEnumerator.Current {
      get { return Current; }
    }

    public Boolean MoveNext() {
      return animalEnumerator.MoveNext();
    }

    public void Reset() {
      animalEnumerator.Reset();
    }

    public void Dispose() {
      animalEnumerator.Dispose();
    }

  }

}

总而言之,拥有一个包含 IEnumerable<Base>IEnumerable<Derived> 工具的集合会给您带来很多麻烦。你想通过使用这个设计来实现什么?

使用泛型 List<T> 和逆变,你可以这样做:

IEnumerable<Turtle> turtles = new List<Turtle>();
IEnumerable<Animal> animals = (IEnumerable<Animal>) turtles;

如果需要,您也可以将 List<T> 替换为您自己的通用集合类型。

鉴于其他答案进入,代码存在许多问题。我想回答你的具体问题:

Why will calling base.Cast<Turtle>() (or any LINQ method on the base element) in any method from the class Turtle fail to compile?

让我们转到规范的第 7.6.8 节。

A base-access is used to access base class members that are hidden by similarly named members in the current class or struct.

您正在访问基本 class 成员吗? 。扩展方法是包含扩展方法的静态 class 的成员,而不是基础 class.

A base-access is permitted only in the block of an instance constructor, an instance method, or an instance accessor.

你在这里很好。

When base.I occurs in a class or struct, I must denote a member of the base class of that class or struct.

同样,Cast<T> 不是基础 class 的成员。

When a base-access references a virtual function member (a method, property, or indexer), the determination of which function member to invoke at run-time (§7.5.4) is changed.

您没有访问虚拟的任何东西。扩展方法是静态的。

The function member that is invoked is determined by finding the most derived implementation of the function member with respect to B (instead of with respect to the run-time type of this, as would be usual in a non-base access). Thus, within an override of a virtual function member, a base-access can be used to invoke the inherited implementation of the function member.

所以现在我们明白了基本访问的目的是什么:启用对在当前类型中被覆盖的虚拟成员的非虚拟分派,或者调用被当前类型new成员隐藏的基础class成员。这不是您尝试使用 base 的目的,因此您注定要失败。停止像这样使用 base 标签外。仅在尝试对虚拟成员进行非虚拟分派或访问隐藏成员时使用它。