Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot use LINQ methods on IEnumerable base class from derived class

I am trying to implement IEnumerable<Turtle> in a class deriving from a base class that already implements IEnumerable<Animal>.

Why will calling base.Cast<Turtle>() (or any LINQ method on the base element) in any method from the class Turtle fail to compile?

It is not possible to replace base with this as it obviously results in a StackOverflowException.

Here is a minimal code sample to replicate the issue:

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"
    }
}

For some reason, replacing base.Cast<Turtle>().GetEnumerator(); with this.OfType<Animal>().Cast<Turtle>().GetEnumerator(); works without throwing a StackOverflowException, but I have no idea why.

like image 851
Erwin Mayer Avatar asked Jan 29 '16 11:01

Erwin Mayer


1 Answers

There are numerous problems with the code given that other answers get into. I want to answer your specific question:

Why will calling base.Cast<Turtle>() (or any LINQ method on the base element) in any method from the class Turtle fail to compile?

Let's go to the specification, section 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.

Are you accessing a base class member? NO. An extension method is a member of the static class that contains the extension method, not the base class.

A base-access is permitted only in the block of an instance constructor, an instance method, or an instance accessor.

You're fine here.

When base.I occurs in a class or struct, I must denote a member of the base class of that class or struct.

Again, Cast<T> is not a member of the base 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.

You are not accessing a virtual anything. Extension methods are static.

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.

So now we see what the purpose of a base access is: to enable a non-virtual dispatch to a virtual member that was overriden in the current type, or to call a base class member that was hidden by a new member in the current type. That is not what you are trying to use base for, and therefore you are doomed to failure. Stop using base off-label like this. Only use it when attempting to do a non-virtual dispatch to a virtual member, or get access to a hidden member.

like image 172
Eric Lippert Avatar answered Sep 28 '22 09:09

Eric Lippert