IEnumerable<T> conversion
Asked Answered
S

5

5

Given the following:


class Base<T> {/*...*/}
class Der<T>: Base<T> {/*...*/}

interface Sth<T>{
  IEnumerable<Base<T>> Foo {get;}
}

// and implementation...
class Impl<T>: Sth<T> {
  public IEnumerable<Base<T>> Foo {
    get {
      return new List<Der<T>>();
    }
  }
}

How can I get this to compile? The error is, obviously, not implicit conversion found from List<Der<T>> to List<Base<T>>. If I cast it explicitly InvalidCastException occurs.

Situated answered 15/10, 2009 at 0:56 Comment(1)
If this topic interests you, you might want to read my massive series on the design of this feature in C# 4.0: blogs.msdn.com/ericlippert/archive/tags/…Quandary
N
6

The conversion you're trying to make isn't supported as of version 3.5 of the .NET Framework. It will be supported in version 4.0 (Visual Studio 2010) due to the addition of generic covariance/contravariance. While this will still not allow you to cast List<Der> to List<Base>, it will allow you to cast IEnumerator<Der> (which the list implements) to IEnumerator<Base>.

In the meantime, you can write your own class that implements IEnumerable<Base> and returns a custom IEnumerator<Base> that simply wraps List<Der>'s IEnumerator<Der>. Alternatively, if you are using the .NET Framework version 3.5, you can use the Cast extension method, as others have suggested.

Nyaya answered 15/10, 2009 at 1:1 Comment(5)
Note that generic variance will not allow a List<Derived> to be cast to a List<Base>, because List<T> has both in and out members of type T. (It will work for using an IEnumerable<Derived> in place of an IEnumerable<Base>.)Kareem
True, but, if I understand correctly, it will allow the OP's code sample to compile. List<Der> implements IEnumerable<Der>, which can be cast to IEnumerable<Base>.Nyaya
Sure, I believe so; I only mentioned it because in the question text he mentioned implicit conversions between the list types, so I wanted to clarify that your comment applied to his code (which uses IEnumerable), not to his supplementary comment (which referred to List).Kareem
That's a fair point. I updated my answer to (hopefully) clarify things a bit.Nyaya
Thanks for explanation. I've just got used to C++ were many things just worked :]]Situated
K
4

List<Der<T>> is not convertible to List<Base<T>> because the latter can have a Base<T> added to it and the former can't.

You can resolve this using the Cast extension method: return new List<Der<T>>().Cast<Base<T>>();

Kareem answered 15/10, 2009 at 1:2 Comment(0)
B
1

To make it compile...

class Impl<T> : Sth<T>
{
    public IEnumerable<Base<T>> Foo
    {
        get
        {
            return new List<Base<T>>(); //change the List type to Base here
        }
    }
} 

You could always do something like this too, which would return the IEnumerable of the Base class from an implementation of Der

class Impl<T> : Sth<T>
{
    public IEnumerable<Base<T>> Foo
    {
        get
        {
            List<Der<T>> x = new List<Der<T>>();

            foreach (Der<T> dt in x)
            {
                yield return dt;
            }
        }
    }
} 
Bivens answered 15/10, 2009 at 1:1 Comment(0)
B
1

You can use LINQ to cast from List<Der<T>> to an IEnumerable<Base<T>>, by using:

class Impl<T>: Sth<T>
{
    public IEnumerable<Base<T>> Foo
    {
        get
        {
            return new List<Der<T>>().Cast<Base<T>>();
        }
    }
}

As the other answers have stated, generic convariance is not supported in v3.5, but you can use LINQ to created a wrapper object that implements IEnumerable<Base<T>>.

Burrton answered 15/10, 2009 at 1:5 Comment(4)
Thanks, that helped. How much will the cast slow the application? Does it make a big impact on the performance?Situated
Get out a stopwatch, try it a few million times, and then you'll know. You're the only one who has access to timing data for your application; we cannot answer that question.Quandary
I've never specifically checked the overhead, but as you're casting to a base type (rather than actually converting the object to a different type, or boxing a value type) I wouldn't have thought of this as expensive code. But Eric is right; we can't tell how your application as a whole will react; if you cast a list with 100 billion items, it'll take a while to cast them all :-DBurrton
Also, on the performance front, remember that LINQ is lazy evaluated; that means that the cast operation is not performed when you call Cast, rather when you iterate over the returned IEnumerable. This might happen in a foreach (in which case, the items are cast individually on each loop through the foreach, spreading the "load") or when you use ToArray or ToList to convert the IEnumerable into a more concrete type. This also means that if you iterate of the IEnumerable twice (e.g. two separate foreach loops) you'll do the entire cast operation twice. Just something to be aware of.Burrton
O
0

These answers helped me out, and I just wanted to post my solution for a similar problem I had.

I wrote an extension method for IEnumerable that automatically casts TSource whenever I want to convert a List<Foo> to an IEnumerable<Bar> (I'm still on 3.5).

public static SpecStatus Evaluate<TSource, TSpecSource>(this IEnumerable<TSource> source, ISpec<IEnumerable<TSpecSource>> spec)
        where TSource : TSpecSource
{
    return spec.Evaluate(source.Cast<TSpecSource>());
}
Ortrud answered 4/4, 2011 at 13:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.