Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic extension method resolution fails

The following program does not compile, because in the line with the error, the compiler chooses the method with a single T parameter as the resolution, which fails because the List<T> does not fit the generic constraints of a single T. The compiler does not recognize that there is another method that could be used. If I remove the single-T method, the compiler will correctly find the method for many objects.

I've read two blog posts about generic method resolution, one from JonSkeet here and another from Eric Lippert here, but I could not find an explanation or a way to solve my problem.

Obviously, having two methods with different names would work, but I like the fact that you have a single method for those cases.

namespace Test
{
  using System.Collections.Generic;

  public interface SomeInterface { }

  public class SomeImplementation : SomeInterface { }

  public static class ExtensionMethods
  {
    // comment out this line, to make the compiler chose the right method on the line that throws an error below
    public static void Method<T>(this T parameter) where T : SomeInterface { }

    public static void Method<T>(this IEnumerable<T> parameter) where T : SomeInterface { }
  }

  class Program
  {
    static void Main()
    {
      var instance = new SomeImplementation();
      var instances = new List<SomeImplementation>();

      // works
      instance.Method();
      
      // Error  1   The type 'System.Collections.Generic.List<Test.SomeImplementation>'
      // cannot be used as type parameter 'T' in the generic type or method
      // 'Test.ExtensionMethods.Method<T>(T)'. There is no implicit reference conversion
      // from 'System.Collections.Generic.List<Test.SomeImplementation>' to 'Test.SomeInterface'.
      instances.Method();

      // works
      (instances as IEnumerable<SomeImplementation>).Method();
    }
  }
}
like image 784
nvoigt Avatar asked Apr 22 '14 11:04

nvoigt


3 Answers

Method resolution says that closer is better. See the blog post for exact rules.

What does the closer mean? Compiler will see if it can find exact match, if it can't find for some reason it will find next possible compatible methods and so forth.

Let's first make that method compile by removing the SomeInterface constraint.

public static class ExtensionMethods
{
    public static void Method<T>(this T parameter) //where T : SomeInterface
    { }

    public static void Method<T>(this IEnumerable<T> parameter) //where T : SomeInterface 
    { }
}

Now compiler is happy to compile, and do note that both method calls Goes to Method(T) rather than Method(IEnumerable<T>). Why is that?

Because Method(T) is closer in the sense that can take any type as the parameter and also it doesn't require any conversion.

Why is Method(IEnumerable<T>) not closer?

It is because you have the compile time type of the variable as List<T>, so it needs a reference conversion from List<T> to IEnumerable<T>. Which is closer but far from doing no conversions at all.

Back to your question.

Why instances.Method(); doesn't compile?

Again, as said earlier to use Method(IEnumerable<T>) we need some reference conversion, so obviously that's not closer. Now we're left with only one method which is very closer is Method<T>. But the problem is you have constrained it with SomeInterface and clearly List<SomeImplementation>() is not convertible to SomeInterface.

The problem is (am guessing) checking for generic constraints happens after the compiler chooses the closer overload. That invalidates the chosen best overload in this case.

You could easily fix it by changing the static type of the variable to IEnumerable<SomeImplementation> that will work and now you know why.

IEnumerable<SomeImplementation> instances = new List<SomeImplementation>();
like image 53
Sriram Sakthivel Avatar answered Oct 12 '22 15:10

Sriram Sakthivel


Have you tried implementing the first one without generics, as it should behave the same:

public static void Method(this SomeInterface parameter) { /*...*/ }

Or, as Dmitry suggested, by calling the second one the following way:

instances.Method<SomeImplementation>();

But here you need to add the <SomeImplementation> to every call...

like image 44
Christoph Fink Avatar answered Oct 12 '22 15:10

Christoph Fink


  1. While I know you dont want it, I think you should really re-think if method names should be the same. I cannot see how the same name can act on an instance, and collection of such instances. For eg, if your method name is Shoot for T, then the other method should sound like ShootThemAll or something similar.

  2. Or else you should make your assignment slightly different:

    IEnumerable<SomeImplementation> instances = new List<SomeImplementation>();
    instances.Method(); //now this should work
    
  3. As a last option, as Dimitry says in comments you have to explicitly specify the type argument.

    instances.Method<SomeImplementation>();
    
like image 1
nawfal Avatar answered Oct 12 '22 17:10

nawfal