I ran across a compilation issue today that baffled me. Consider these two container classes.
public class BaseContainer<T> : IEnumerable<T>
{
public void DoStuff(T item) { throw new NotImplementedException(); }
public IEnumerator<T> GetEnumerator() { }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { }
}
public class Container<T> : BaseContainer<T>
{
public void DoStuff(IEnumerable<T> collection) { }
public void DoStuff <Tother>(IEnumerable<Tother> collection)
where Tother: T
{
}
}
The former defines DoStuff(T item)
and the latter overloads it with DoStuff <Tother>(IEnumerable<Tother>)
specifically to get around the absence of covariance/contravariance of C# (until 4 I hear).
This code
Container<string> c = new Container<string>();
c.DoStuff("Hello World");
hits a rather strange compilation error. Note the absence of <char>
from the method call.
The type 'char' cannot be used as type parameter 'Tother' in the generic type or method 'Container.DoStuff(System.Collections.Generic.IEnumerable)'. There is no boxing conversion from 'char' to 'string'.
Essentially, the compiler is trying to jam my call to DoStuff(string)
into Container.DoStuff<char>(IEnumerable<char>)
because string
implements IEnumerable<char>
, rather than use BaseContainer.DoStuff(string)
.
The only way I've found to make this compile is to add DoStuff(T)
to the derived class
public class Container<T> : BaseContainer<T>
{
public new void DoStuff(T item) { base.DoStuff(item); }
public void DoStuff(IEnumerable<T> collection) { }
public void DoStuff <Tother>(IEnumerable<Tother> collection)
where Tother: T
{
}
}
Why is the compiler trying to jam a string as IEnumerable<char>
when 1) it knows it can't (given the presence of a compilation error) and 2) it has a method in the base class that compiles fine? Am I misunderstanding something about generics or virtual method stuff in C#? Is there another fix other than adding a new DoStuff(T item)
to Container
?
Edit
Ok... I think I see your confusion now. You would have expected DoStuff(string) to have kept the parameter as a string and walked the BaseClass Method List first looking for a suitable signature, and failing that fallback to trying to cast the parameter to some other type.
But it happened the other way around... Instead Container.DoStuff(string)
went, meh "theres a base class method there that fits the bill, but I'm going to convert to an IEnumerable and have a heart attack about what's available in the current class instead...
Hmmm... I'm sure Jon or Marc would be able to chime in at this point with the specific C# Spec paragraph covering this particular corner case
Original
Both Methods expect an IEnumerable Collection
You're passing an individual string.
The compiler is taking that string and going,
Ok, I have a string, Both methods expect an
IEnumerable<T>
, So I'll turn this string into anIEnumerable<char>
... DoneRight, Check the first method... hmmm... this class is a
Container<string>
but I have anIEnumerable<char>
so that's not right.Check the second method, hmmm.... I have an
IEnumerable<char>
but char doesn't implement string so that's not right either.
COMPILER ERROR
So what#s the fix, well it completely depends what your trying to achieve... both of the following would be valid, essentially, your types usage is just incorrect in your incarnation.
Container<char> c1 = new Container<char>();
c1.DoStuff("Hello World");
Container<string> c2 = new Container<string>();
c2.DoStuff(new List<string>() { "Hello", "World" });
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With