I think it has something to do with the fact that char is a value type and string is a reference type. It looks like you're defining
TOther : T
and char does not derive from string.
I think it has something to do with the fact that char is a value type and string is a reference type. It looks like you're defining
TOther : T
and char does not derive from string.
The compiler will try to match the parameter to IEnumerable<T>
. The String type implements IEnumerable<char>
, so it assumes that T is "char".
After that, the compiler checks the other condition "where OtherT : T", and that condition is not met. Hence the compiler error.
My GUESS, and this is a guess because I don't really know, is that it first looks in the derived class to resolve the method call (because your object is that of the derived type). If, and only if it can't, it moves on to looking at the base classes methods to resolve it. In your case, since it CAN resolve it using the
DoStuff <Tother>(IEnumerable<Tother> collection)
overload, it tried to jam it into that. So it CAN resolve it as far as the parameter is concerned, but then it hits a snag on the constraints. At that point, it's already resolved your overload, so it doesn't look any further, but just throws up an error. Make sense?
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" });
As Eric Lippert explained, the compiler chooses the DoStuff<Tother>(IEnumerable<Tother>) where Tother : T {}
method because it chooses methods before checking constraints. Since string can do IEnumerable<>
, the compiler matches it to that child class method.
The compiler is working correctly as described in the C# specification.
The method resolution order you desire can be forced by implementing DoStuff as an extension method.
Extension methods are checked after base class methods, so it will not try to match string
against DoStuff
's IEnumerable<Tother>
until after it has tried to match it against DoStuff<T>
.
The following code demonstrates the desired method resolution order, covariance, and inheritance. Please copy/paste it into a new project.
This biggest downside I can think of so far is that you can not use base
in the overriding methods, but I think there are ways around that (ask if you are interested).
using System;
using System.Collections.Generic;
namespace MethodResolutionExploit
{
public class BaseContainer<T> : IEnumerable<T>
{
public void DoStuff(T item) { Console.WriteLine("\tbase"); }
public IEnumerator<T> GetEnumerator() { return null; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return null; }
}
public class Container<T> : BaseContainer<T> { }
public class ContainerChild<T> : Container<T> { }
public class ContainerChildWithOverride<T> : Container<T> { }
public static class ContainerExtension
{
public static void DoStuff<T, Tother>(this Container<T> container, IEnumerable<Tother> collection) where Tother : T
{
Console.WriteLine("\tContainer.DoStuff<Tother>()");
}
public static void DoStuff<T, Tother>(this ContainerChildWithOverride<T> container, IEnumerable<Tother> collection) where Tother : T
{
Console.WriteLine("\tContainerChildWithOverride.DoStuff<Tother>()");
}
}
class someBase { }
class someChild : someBase { }
class Program
{
static void Main(string[] args)
{
Console.WriteLine("BaseContainer:");
var baseContainer = new BaseContainer<string>();
baseContainer.DoStuff("");
Console.WriteLine("Container:");
var container = new Container<string>();
container.DoStuff("");
container.DoStuff(new List<string>());
Console.WriteLine("ContainerChild:");
var child = new ContainerChild<string>();
child.DoStuff("");
child.DoStuff(new List<string>());
Console.WriteLine("ContainerChildWithOverride:");
var childWithOverride = new ContainerChildWithOverride<string>();
childWithOverride.DoStuff("");
childWithOverride.DoStuff(new List<string>());
//note covariance
Console.WriteLine("Covariance Example:");
var covariantExample = new Container<someBase>();
var covariantParameter = new Container<someChild>();
covariantExample.DoStuff(covariantParameter);
// this won't work though :(
// var covariantExample = new Container<Container<someBase>>();
// var covariantParameter = new Container<Container<someChild>>();
// covariantExample.DoStuff(covariantParameter);
Console.ReadKey();
}
}
}
Here is the output:
BaseContainer:
base
Container:
base
Container.DoStuff<Tother>()
ContainerChild:
base
Container.DoStuff<Tother>()
ContainerChildWithOverride:
base
ContainerChildWithOverride.DoStuff<Tother>()
Covariance Example:
Container.DoStuff<Tother>()
Can you see any problems with this work around?
I'm not really clear on what you're trying to accomplish, what's stopping you from just using two methods, DoStuff(T item) and DoStuff(IEnumerable<T> collection)?