IEnumerable<T> as IEnumerable<BaseClassOfT>
A few weeks ago I touched on the substitutability of generic types and how Collection<BaseClass>
is never substitutable for Collection<SubClass>
. This is because while the read facilities would be substitutable the write ones would not be. A Collection
One approach for dealing with collections in general recommended in .NET Framework Design Guidelines is to expose IEnumerator
or IEnumerable
interfaces rather than the collection itself – especially if you intend on it being read-only.
But even IEnumerator<T>
and IEnumerable<T>
can’t help you if you need to return not IEnumerable<T>
inherits from the non-generic IEnumerable
solves your problem.
C#’s generics aren’t clever enough to realize that an instantiated generic class might be substitutable for another generic class although. Wilco points out that the .NET CLR supports “generic contravariance” but C# doesn’t yet expose it.
In the mean time trying to passing back IEnumerator<T>
to something expecting IEnumerator<BaseClassOfT>
will result in a compiler error. Try cast ing it and a run-time error awaits.
One option would be to create a whole new Collection<BaseClass>
and copy each SubClass element into it but this is hardly efficient or elegant.
What would be really cool is if you could somehow wrap it like;
public IEnumerable<BaseClass> BaseClasses {
return new BaseEnumerable<BaseClass, SubClass>(subClassCollection);
}
Of course you’d need BaseEnumerator and BaseEnumerable generic classes to do this and preferably they’d be able to enforce that SubClass is of BaseClass at compile time.
So here are two classes to do just that for your enumerable pleasure.
using System;
using System.Collections;
using System.Collections.Generic;
public class BaseEnumerable<TBase, TSub> : IEnumerable<TBase> where TSub : TBase {
private IEnumerable<TSub> subEnumerable;
public BaseEnumerable(IEnumerable<TSub> subEnumerable) {
this.subEnumerable = subEnumerable;
}
public IEnumerator<TBase> GetEnumerator() {
return new BaseEnumerator<TBase, TSub>(subEnumerable);
}
IEnumerator IEnumerable.GetEnumerator() {
return subEnumerable.GetEnumerator();
}
}
public class BaseEnumerator<TBase, TSub> : IEnumerable<TBase> where TSub : TBase {
private IEnumerator<TSub> subEnumerator;
public BaseEnumerator(IEnumerable<TSub> subEnumerable) {
subEnumerator = subEnumerable.GetEnumerator();
}
public BaseEnumerator(IEnumerable<TSub> subEnumerator) {
this.subEnumerator = subEnumerator;
}
public TBase Current {
get { return subEnumerator.Current; }
}
public bool MoveNext() {
return subEnumerator.MoveNext();
}
public void Reset() {
subEnumerator.Reset();
}
}
They work clean and fast for me but I’m no expert on the responsibilities of implementing the Dispose pattern. Warranty is not included and your mileage may vary.
[)amien
10 responses