无法在派生 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
标签外。仅在尝试对虚拟成员进行非虚拟分派或访问隐藏成员时使用它。
我正在尝试在 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
.
但是,您可以投射 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 classTurtle
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
标签外。仅在尝试对虚拟成员进行非虚拟分派或访问隐藏成员时使用它。