Is it wrong to cast an enumerator of a child class to an enumerator of a parent class?
-
04-07-2019 - |
Question
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();
}
Solution
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();
OTHER TIPS
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.
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;
}
}
}
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 IParentThing
s should be able to recieve a ChildThing2
. But a list of ChildThing1
s cannot handle a ChildThing2
, or indeed any implementor of IParentThing
other than ChildThing1
- in other words, if the List<ChildThing1>
was allowed to cast as a List<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.
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();