[英]C#: Using LINQ on a derived iterator class when both the derived class and its base implement IEnumerable
[英]Cannot use LINQ methods on IEnumerable base class from derived class
我试图在从已经实现IEnumerable<Animal>
的基类派生的类中实现IEnumerable<Turtle>
IEnumerable<Animal>
。
为什么在类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<Turtle>().GetEnumerator();
with this.OfType<Animal>().Cast<Turtle>().GetEnumerator();
在没有抛出StackOverflowException
,但我不知道为什么。
在给出其他答案的情况下,代码存在许多问题。 我想回答你的具体问题:
为什么在类
Turtle
任何方法中调用base.Cast<Turtle>()
(或基本元素上的任何LINQ方法)都无法编译?
我们来看规范,第7.6.8节。
base-access用于访问在当前类或结构中由类似命名的成员隐藏的基类成员。
您是否正在访问基类成员? 不 。 扩展方法是静态类的成员,它包含扩展方法,而不是基类。
仅允许在实例构造函数,实例方法或实例访问器的块中进行基本访问。
你在这里很好
当base.I出现在类或结构中时,我必须表示该类或结构的基类的成员。
同样, Cast<T>
不是基类的成员。
当base-access引用虚函数成员(方法,属性或索引器)时,将确定在运行时调用哪个函数成员(第7.5.4节)。
您无法访问任何虚拟内容。 扩展方法是静态的。
调用的函数成员是通过查找关于B的函数成员的最多派生实现来确定的(而不是相对于其的运行时类型,如非基本访问中通常的那样)。 因此,在虚函数成员的重写中,可以使用基本访问来调用函数成员的继承实现。
现在我们看到基本访问的目的是什么: 启用对当前类型中覆盖的虚拟成员的非虚拟分派 ,或者调用当前类型中的new
成员隐藏的基类成员 。 这不是你想要使用的base
,因此你注定要失败。 像这样停止使用base
off-label。 仅在尝试对虚拟成员执行非虚拟分派或访问隐藏成员时才使用它。
埃里克利珀已经声明之前,这是一个有点意识的设计决定在这里 。 在您首先可以访问基类实现的情况下,您从未打算使用扩展方法。
如果你考虑一下,你也不需要在这里做到这一点。 创建受保护的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
及其衍生物进行枚举
我会回答这个问题:
为什么在类Turtle中的任何方法中调用base.Cast()(或基本元素上的任何LINQ方法)都无法编译?
该异常的原因是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所述:
仅在构造函数,实例方法或实例属性访问器中允许基类访问。
您的方法存在几个问题。 TurtleEnumerable
实现了IEnumerable<Animal>
和IEnumerable<Turtle>
。 为了能够在foreach
循环中使用TurtleEnumerable
实例,您必须将其转换为要编译的代码:
foreach (var turtle in (IEnumerable<Turtle>) turtleEnumerable)
您还使用显式接口实现来隐藏通用GetEnumerator()
方法。 您必须这样做,因为您不能单独对返回类型执行重载解析,并且两个通用GetEnumerator()
方法仅因返回类型而不同。
但是,这意味着TurtleEnumerable
方法无法调用基本的GetEnumerator()
方法。 这样做的原因是base
行为不像“base”类型的变量。 相反,它是一个保留字,只能用于调用基类方法。 对此的推论是扩展方法不能与base
一起使用。 此外,你可以不投base
在基类,所以显式接口实现不是通过调用base
。
但是,你可以投this
,而是因为一般GetEnumerator()
上TurtleEnumerable
隐藏了通用GetEnumerator()
上AnimalEnumerable
您将无法调用到基类,所以你会得到一个堆栈溢出,因为在某些时候执行TurtleEnumerable.GetEnumerator()
将调用相同的GetEnumerator
。
要使代码编译,您需要在基类中创建protected IEnumerator<Animal> GetEnumerator()
方法,并创建自己的TurtleEnumerator
类,它包装您可以通过调用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>
。
为什么要在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....
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.