我试图在一个派生自已实现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
的情况下工作,但我不知道为什么。
答案 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....
}
}