【问题标题】:Cannot use LINQ methods on IEnumerable base class from derived class无法在派生类的 IEnumerable 基类上使用 LINQ 方法
【发布时间】:2016-05-07 02:45:41
【问题描述】:

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

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

不可能用this 替换base,因为它显然会导致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&lt;Turtle&gt;().GetEnumerator(); 替换为this.OfType&lt;Animal&gt;().Cast&lt;Turtle&gt;().GetEnumerator(); 不会抛出StackOverflowException,但我不知道为什么。

【问题讨论】:

  • 这应该如何工作?这些是不同的类 - TurtleEnumerable 不会以任何方式从 Turtle 继承,它只是实现接口 IEnumerable
  • TurtleEnumerable 确实继承自 AnimalEnumerable 类,因此它应该可以访问基本元素。

标签: c# inheritance interface ienumerator


【解决方案1】:

我会回答这个问题:

为什么要调用 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); 

如果你用那个替换你的代码,编译器会给出更合适的错误信息:Use of keyword base is not valid in this context.

如MSDN中所说:

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

【讨论】:

  • 为什么不能使用 base 关键字调用扩展方法?您是否知道访问基本元素的解决方法(除了丑陋的this.OfType&lt;Animal&gt;().Cast&lt;Turtle&gt;().GetEnumerator();)?
  • 您不能从静态方法调用base 来访问迭代器和元素。
  • @ErwinMayer 抱歉,我赶时间。我将尝试更深入地解释它。
  • 有道理。如果我必须改用this,我该如何实现我想要的?
  • @ErwinMayer 可能是:) 谢谢。试图解释。实际上我从来没有遇到过这个错误信息,所以我花了一些时间来改进我的答案。没问题,我很高兴今天我学到了更多的东西。
【解决方案2】:

Eric Lippert 之前曾表示,这是一个有意识的设计决定here。在您首先可以访问基类的实现的情况下,您永远不会使用扩展方法。

如果您考虑一下,您也不需要在这里执行此操作。创建一个受保护的 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 及其派生类等

【讨论】:

  • 您不能将IEnumerator&lt;Animal&gt; 转换为IEnumerator&lt;Turtle&gt;,所以这段代码不会编译。
  • 您必须在基类中创建一个受保护的方法,该方法返回一个 IEnumerable 供派生类使用。
  • 我最终只是以一种受保护的方式暴露了List&lt;Animal&gt; Animals,这样我就可以从Turtle 类中调用this.Animals.Cast&lt;Turtle&gt;().GetIterator()。
  • 请问您为什么需要 XYZEnumerable 类?为什么不让 IAnimal 实现 IEnumerable 并完成它。然后你可以通过接口枚举所有你想要的Animal和Turtle
  • 我在一个相当复杂的接口组合案例中,在我的实际用例中实际上还涉及其他接口。一切都可能被重构以避免这种情况,但现在我必须找到一个可行的解决方案。
【解决方案3】:

为什么要在 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....
        }
    }

【讨论】:

  • 我有充分的理由需要实现两个都实现 IEnumerable 的接口。我使用的示例只是一个简化。然而,我最终只是以一种受保护的方式暴露了List&lt;Animal&gt; Animals,这样我就可以从Turtle 类中调用this.Animals.Cast&lt;Turtle&gt;().GetIterator()。
【解决方案4】:

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

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

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

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

但是,您可以强制转换 this,但由于 TurtleEnumerable 上的通用 GetEnumerator() 隐藏了 AnimalEnumerable 上的通用 GetEnumerator(),您将无法调用基类,因此您将获得堆栈溢出因为在某些时候TurtleEnumerable.GetEnumerator() 的实现将调用相同的GetEnumerator。

要编译您的代码,您需要在您的基类中创建一个protected IEnumerator&lt;Animal&gt; 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&lt;Base&gt; 和 IEnumerable&lt;Derived&gt; 都会给你带来很多麻烦。您想通过使用这种设计来实现什么目标?

使用通用 List&lt;T&gt; 和逆变,您可以执行以下操作:

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

如果需要,您还可以将 List&lt;T&gt; 替换为您自己的通用集合类型。

【讨论】:

    【解决方案5】:

    考虑到其他答案,代码存在许多问题。我想回答你的具体问题:

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

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

    基访问用于访问被当前类或结构中类似名称的成员隐藏的基类成员。

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

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

    你在这里很好。

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

    同样,Cast&lt;T&gt; 不是基类的成员。

    当基本访问引用虚函数成员(方法、属性或索引器)时,在运行时(第 7.5.4 节)调用哪个函数成员的决定会改变。

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

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

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

    【讨论】:

      猜你喜欢
      • 2018-10-12
      • 1970-01-01
      • 2019-07-25
      • 2014-09-05
      • 1970-01-01
      • 1970-01-01
      • 2016-01-01
      • 2020-03-24
      • 1970-01-01
      相关资源
      最近更新 更多