views:

846

answers:

5

I've got an error in my build which says:

Error 12 Cannot implicitly convert type 'System.Collections.Generic.IEnumerator< BaseClass>' to 'System.Collections.Generic.IEnumerator< IParentClass>'. An explicit conversion exists (are you missing a cast?)

Is it wrong to simply cast it away?

This is my code:

public Dictionary<Int32, BaseClass> Map { get; private set; }

public IEnumerator<BaseClass> GetEnumerator()
     {
      return this.Map.Values.GetEnumerator();
     }

public IEnumerator<IParentClass> IEnumerable<IParentClass>.GetEnumerator()
     {
      return this.GetEnumerator(); // ERROR!
     }

My question is, can I just change this line:

return this.GetEnumerator();

to:

return (IEnumerator<IParentClass>)this.GetEnumerator();

(without any bad side effects)?

Accepted Answer:
I've changed the function to the following (after reading Jon Skeet's post):

IEnumerator<IParentClass> IEnumerable<IParentClass>.GetEnumerator()
     {
      return this.Map.Values.Cast<IParentClass>().GetEnumerator();
     }
A: 

IEnumerator<BaseClass> and IEnumerator<ParentClass> are unrelated, eventhough their generic parameters are. I would instead use a LINQ Select extension method like so:

return this.Select(x => (IParentClass)x).GetEnumerator();

or the Cast extension method:

return this.Cast<IParentClass>().GetEnumerator();
spoulson
No can do with IEnumerator - you need IEnumerable. (In which case just use Cast() in the first place :)
Jon Skeet
Jon beat me to it... but same again...
Marc Gravell
I've corrected the example. I also included the Cast() method.
spoulson
+2  A: 

No, you can't, at least in C# 3.0 and below interface variance is not supported. See Eric Lippert's excellent series on this, and specifically this one.

Ilya Ryzhenkov
+5  A: 

No you can't, because generics aren't covariant in C# at the moment. .NET itself has some support (for delegates and interfaces) but it's not really used yet.

If you were returning IEnumerable<BaseClass> instead of IEnumerator<BaseClass> (and assuming .NEt 3.5) you could use Enumerable.Cast - but you'll currently need to write your own extension method, e.g.

public static IEnumerator<TParent> Upcast<TParent, TChild>
    (this IEnumerator<TChild> source)
    where TChild : TParent
{
    while (source.MoveNext())
    {
        yield return source.Current;
    }
}

Alternatively in your case you could use Cast earlier:

return this.Map.Values.Cast<BaseClass>().GetEnumerator();
Jon Skeet
A: 

No, it isn't safe, see below:

using System.Collections.Generic; class Foo { } class Bar : Foo { }

static class Program
{
    static IEnumerator<Foo> GetBase() {
        yield return new Foo();
        yield return new Bar();
    }
    static IEnumerator<Bar> GetDerived()
    {
        return (IEnumerator<Bar>)GetBase();
    }
    static void Main()
    {
        var obj = GetDerived(); // EXCEPTION
    }
}

However, you should be able to use an iterator block to do the cast for you?

static IEnumerator<Bar> GetDerived()
{
    using (IEnumerator<Foo> e = GetBase())
    {
        while (e.MoveNext())
        {
            // or use "as" and only return valid data
            yield return (Bar)e.Current;
        }
    }
}
Marc Gravell
A: 

To reason why this isn't appropriate, picture instead of an Enumerator, a List. Both use generics - the compiler doesn't handle either one in a special way in relation to generic arguments.

void doStuff() {
    List<IParentThing> list = getList();
    list.add(new ChildThing2());
}

List<IParentThing> getList() {
    return new List<ChildThing1>();  //ERROR!
}

This first method is fine - a list of IParentThings should be able to recieve a ChildThing2. But a list of ChildThing1s cannot handle a ChildThing2, or indeed any implementor of IParentThing other than ChildThing1 - in other words, if the List&lt;ChildThing1> was allowed to cast as a List&lt;IParent>, it would have to be able to deal with all subclasses of IParentThing, not just IParentThing and ChildThing1.

Note that Java generics have a way to say that "I want a list of anything that inherits from this" in addition to "I want a list of anything that this inherits," which allows for more interesting (and in my opinion elegant) solutions to some problems.

Chris Marasti-Georg