Generics, inheritance, and failed solution to C # compiler method

Today I ran into a compilation problem that confused me. Consider these two classes of containers.

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>) to bypass the lack of covariance / contravariance (up to 4, I hear) ..

This code

 Container<string> c = new Container<string>(); c.DoStuff("Hello World"); 

gets into a rather strange compilation error. Note the absence of a <char> from a method call.

The type 'char' cannot be used as a parameter of type 'Tother' in the generic type or method 'Container.DoStuff (System.Collections.Generic.IEnumerable)'. There is no box conversion from 'char' to 'string'.

Essentially, the compiler is trying to hush my call to DoStuff(string) on Container.DoStuff<char>(IEnumerable<char>) , because string implements IEnumerable<char> rather than using BaseContainer.DoStuff(string) .

The only way I found this compilation 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 does the compiler try to hush the line as an IEnumerable<char> when 1) it knows that it cannot (given the compilation error), and 2) does it have a method in the base class that compiles fine? Don't I really understand something about generics or virtual method materials in C #? Is there any other fix besides adding new DoStuff(T item) to the Container ?

+6
generics c # compilation
source share
6 answers

Edit

Okay ... I think now I see your confusion. You would expect DoStuff (string) to save the parameter as a string and first enumerate the BaseClass methods list, looking for a suitable signature, and fail to reject an attempt to apply this parameter to another type.

But it happened the other way around ... Instead of Container.DoStuff(string) went, meh "theres a base class method there that is suitable for counting, but I'm going to convert to IEnumerable and have a heart attack about what's available in the current class instead. ..

Hmmm ... I'm sure John or Mark will be able to call back at this point with a specific C # Spec paragraph covering this particular corner case

Original

Both methods expect IEnumerable Collection

You pass a separate line.

The compiler takes this line and goes,

Ok, I have a string, Both methods expect IEnumerable<T> , so I will turn this string into IEnumerable<char> ... Done

Right, check out the first method ... hmmm ... this is a Container<string> class, but I have an IEnumerable<char> so that it doesn't go right.

Check out the second method, hmm ... have an IEnumerable<char> , but char does not implement the string, so it is also wrong.

COMPUTER ERROR

So, what a fix, well, it totally depends on what you are trying to achieve ... both of them would be valid, in fact, using your types is just wrong 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" }); 
+3
source share

As Eric Lippert explained, the compiler chooses the DoStuff<Tother>(IEnumerable<Tother>) where Tother : T {} method because it selects the methods before checking the constraints. Since a string can execute IEnumerable<> , the compiler maps it to this method of the child class. The compiler is working correctly as described in the C # specification.

The desired method resolution order can be enforced using DoStuff as an extension method. Extension methods are checked after the methods of the base class, so it will not try to match the string with DoStuff IEnumerable<Tother> until it tries to match it with DoStuff<T> .

The following code demonstrates the desired resolution order, covariance and inheritance. Copy / paste it into a new project.

This biggest flaw that I can guess about is that you cannot use base in overriding methods, but I think there are ways around this (ask if you're 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 result:

 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 job?

+3
source share

The compiler will try to map the parameter to IEnumerable <T> . The String type implements an IEnumerable <char> , so it assumes T is a "char".

After that, the compiler checks another condition "where OtherT: T", and this condition is not met. Therefore, a compiler error.

+2
source share

My GUESS, and this assumption, because I really don’t know, is that it first searches for a derived class to resolve the method call (because your object is an object of a derived type). If and only if he cannot, he proceeds to consider the methods of base classes to solve it. In your case, since it can resolve it with

 DoStuff <Tother>(IEnumerable<Tother> collection) 

he tried to drive him into it. That way, he can resolve it to a parameter, but then he gets caught with restrictions. At this point, it already allowed your overload, so it no longer looks, but simply causes an error. It makes sense?

+2
source share

I think this is because char is the value type and the string is the reference type. It looks like you are defining

 TOther : T 

and char is not inferred from the string.

+1
source share

I don’t quite understand what you are trying to do, which prevents you from using only two methods: DoStuff(T item) and DoStuff(IEnumerable<T> collection)?

0
source share

All Articles