不能在派生类的IEnumerable基类上使用LINQ方法

时间:2016-01-29 11:21:55

标签: c# inheritance interface ienumerator

我试图在一个派生自已实现IEnumerable<Turtle>的基类的类中实现IEnumerable<Animal>

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

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

以下是复制问题的最小代码示例:

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();可以在不抛出StackOverflowException的情况下工作,但我不知道为什么。

5 个答案:

答案 0 :(得分:10)

在给出其他答案的情况下,代码存在许多问题。我想回答你的具体问题:

  

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

让我们按照规范7.6.8节进行操作。

  

base-access用于访问在当前类或结构中由类似命名的成员隐藏的基类成员。

您是否正在访问基类成员? 即可。扩展方法是包含扩展方法的静态类的成员,而不是基类。

  

仅允许在实例构造函数,实例方法或实例访问器的块中进行基本访问。

你在这里很好。

  

当base.I出现在类或结构中时,我必须表示该类或结构的基类的成员。

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

  

当base-access引用虚函数成员(方法,属性或索引器)时,确定在运行时调用哪个函数成员(第7.5.4节)。

您无法访问任何虚拟内容。扩展方法是静态的。

  

调用的函数成员是通过查找关于B的函数成员的最派生实现来确定的(而不是相对于此的运行时类型,如非基本访问中通常的那样) 。因此,在虚函数成员的覆盖中,可以使用base-access来调用函数成员的继承实现。

现在我们看到基本访问的目的是什么:启用非虚拟调度到在当前类型中覆盖的虚拟成员,或调用基础被当前类型中的new成员隐藏的类成员。这不是你试图使用base的原因,因此你注定要失败。像这样停止使用base off-label。仅在尝试对虚拟成员进行非虚拟分派或访问隐藏成员时才使用它。

答案 1 :(得分:5)

Eric Lippert之前曾说过,这是一个有点有意识的设计决定here。在您首先可以访问基类实现的情况下,您从未打算使用扩展方法。

如果你考虑一下,你也不需要在这里做。创建受保护的GetEnumerator属性或方法并使用它!基本面向对象;这里没有必要折磨linq。

修改 有人指出,我以前的建议没有用。所以,我建议不要实现两个不同的IEnumerable接口,因为这会引起很多令人头疼的事情。

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

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和它们的衍生物进行枚举

答案 2 :(得分:2)

我会回答这个问题:

  

为什么要调用base.Cast()(或者基础上的任何LINQ方法)   element)在Turtle类的任何方法中都无法编译?

该异常的原因是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
    }
}

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

 Extensions.Method2(base); 

如果您使用该代码替换代码,编译器将提供更合适的错误消息:在此上下文中使用关键字base无效。

正如MSDN所说:

  

仅在构造函数(实例)中允许基类访问   方法或实例属性访问器。

答案 3 :(得分:2)

您的方法存在一些问题。 TurtleEnumerable同时实现IEnumerable<Animal>IEnumerable<Turtle>。为了能够在TurtleEnumerable循环中使用foreach实例,您必须将其转换为要编译的代码:

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

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

但是,这意味着TurtleEnumerable方法无法调用基本GetEnumerator()方法。这样做的原因是base的行为不像#34; base&#34;类型的变量。相反,它是一个保留字,只能用于调用基类方法。对此的推论是,扩展方法不能与base一起使用。此外,您无法强制转换base,因此基类上的显式接口实现无法通过base调用。

但是,您可以投射this,但由于GetEnumerator()上的通用TurtleEnumerable隐藏GetEnumerator()上的通用AnimalEnumerable,您将无法调用TurtleEnumerable.GetEnumerator()基类,因此您将获得堆栈溢出,因为在某些时候GetEnumerator的实现将调用相同的protected IEnumerator<Animal> GetEnumerator()

要使代码编译,您需要在基类中创建一个TurtleEnumerator方法,并创建自己的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>

如果需要,您还可以使用自己的通用集合类型替换array_walk_recursive

答案 4 :(得分:1)

为什么要在TurtleEnumerator类上实现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....
        }
    }