Explicit specification of range variable required when hiding inherited IEnumerable<T>


A

Anders Borum

Hello

I'm developing an API with a number of concrete collections that inherit
from an abstract collection hierarchy. Each inheritance level provides an
implementation of IEnumerable<T>, thus allowing the developer to take full
advantage of the LINQ extensions without having to start with a cast
operation to a concrete type that matches the collection.

I was surprised that the C# compiler was unable to resolve the type of the
range variable when writing LINQ queries against a collection that hides an
inherited IEnumerable<T>. Consider the following example (just to be clear -
I'm fully aware that it is possible to write the following using a generic
collection instead, but please follow me);

public class Animal {}
public class Tiger : Animal {}

public abstract class AnimalCollection : IEnumerable<Animal>
{
public IEnumerator<Animal> GetEnumerator()
{
throw new NotImplementedException();
}

IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}

public class TigerCollection : AnimalCollection, IEnumerable<Tiger>
{
public new IEnumerator<Tiger> GetEnumerator()
{
throw new NotImplementedException();
}
}

public void ProcessAnimals()
{
AnimalCollection animals = new TigerCollection();
TigerCollection tigers = new TigerCollection();

// works
var a1 = from a in animals select a;

// fails (requires explicit specification of t as Tiger)
var t1 = from t in tigers select t;

// works when specifying Tiger explicitly
var t1 = from Tiger t in tigers select t;
}

Error 1 Could not find an implementation of the query pattern for source
type 'TigerCollection'. 'Select' not found. Consider explicitly specifying
the type of the range variable 't'.

We were just wondering why the C# compiler is unable to resolve the type
Tiger from the TigerCollection (now that it explicit implements
IEnumerable<Tiger>.
 
Ad

Advertisements

M

Marc Gravell

From the compiler's perspective, it has both IEnumerable<Tiger> and
IEnumerable<Animal> to pick from, and neither is "better", so it can't
make the decision on its own. Similarly, if you declare:

static void Test(IEnumerable<Animal> animals) {}
static void Test(IEnumerable<Tiger> tigers) {}

and call Test(tigers);

The call is ambiguous between the following methods or properties:
'Program.Test(System.Collections.Generic.IEnumerable<Animal>)' and
'Program.Test(System.Collections.Generic.IEnumerable<Tiger>)'

but add a static void Test(TigerCollection tigers) { } and it can do
it.

But it tells you how to fix it (and it is easy to do so), which is
usually enough...

Marc
 
M

Marc Gravell

To clarify - it *would* be able to pick a better candidate if the
difference was between Foo<T> and Bar<T> where Foo<T> : Bar<T>, but
this is very different to SomeType<Foo> vs SomeType<Bar> where Foo :
Bar.

Gotta love variance ;-p

Marc
 
A

Anders Borum

From the compiler's perspective said:
IEnumerable<Animal> to pick from, and neither is "better".

Well, I'm explicit asking for the enumerable object of IEnumerable<Tiger>. I
guess this one should be better than defaulting to the IEnumerable<Animal>.
 
A

Anders Borum

To clarify - it *would* be able to pick a better candidate if the
difference was between Foo<T> and Bar<T> where Foo<T> : Bar<T>, but
this is very different to SomeType<Foo> vs SomeType<Bar> where Foo :
Bar.

Don't get me started on variance ;-)
 
Ad

Advertisements

M

Marc Gravell

Well, I'm explicit asking for the enumerable object of IEnumerable<Tiger>.

By adding the "Tiger"? yes.

Interestingly (maybe) this is a rare example of where knowing *less*
about a variable is better:

TigerCollection tigersOrig = new TigerCollection();
IEnumerable<Tiger> tigers = tigersOrig;
....
var t1 = from t in tigers select t;

Now the compiler doesn't have choices - it only knows about
IEnumerable<Tiger>, so it works.

Marc
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top