通用扩展方法解析失败

时间:2014-04-22 11:00:16

标签: c# generics extension-methods overload-resolution

以下程序无法编译,因为在出​​错的行中,编译器选择使用单个T参数作为分辨率的方法,该方法失败,因为List<T>不适合通用单T的约束。编译器无法识别可以使用的其他方法。如果我删除单个 - T方法,编译器将正确地找到许多对象的方法。

我已经阅读了两篇关于通用方法解析的博客文章,其中一篇来自JonSkeet here,另一篇来自Eric Lippert here,但我无法找到解释或解决问题的方法

显然,使用两个具有不同名称的方法会起作用,但我喜欢这样一个事实:对于这些情况,你只有一种方法。

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();
    }
  }
}

3 个答案:

答案 0 :(得分:6)

方法解析表明closer is better。有关准确的规则,请参阅博客文章。

近点是什么意思?编译器将查看是否可以找到完全匹配,如果由于某种原因它找不到下一个可能的兼容方法等等。

让我们首先通过删除SomeInterface约束来编译该方法。

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 
    { }
}

现在编译器很乐意编译,并注意两个方法调用都转到Method(T)而不是Method(IEnumerable<T>)。那是为什么?

因为Method(T)更接近于可以将任何类型作为参数,并且它不需要任何转换。

  

为什么Method(IEnumerable<T>)不会更近?

这是因为您将变量的编译时类型设置为List<T>,因此需要从List<T>IEnumerable<T>的引用转换。哪个更接近,但根本没有做任何转换。

回到你的问题。

  

为什么instances.Method();无法编译?

同样,如前所述使用Method(IEnumerable<T>)我们需要一些参考转换,所以显然不会更接近。现在我们只剩下一种非常接近的方法Method<T>。但问题是您已使用SomeInterface限制它,并且List<SomeImplementation>()显然无法转换为SomeInterface

问题是(猜测)在编译器选择更近的重载之后检查通用约束。在这种情况下,这会使选择的最佳重载无效。

您可以通过将变量的静态类型更改为可用的IEnumerable<SomeImplementation>来轻松修复它,现在您知道原因了。

IEnumerable<SomeImplementation> instances = new List<SomeImplementation>();

答案 1 :(得分:1)

您是否尝试过在没有泛型的情况下实现第一个,因为它的行为应该相同:

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

或者,正如德米特里建议的那样,通过以下方式呼叫第二个:

instances.Method<SomeImplementation>();

但是,您需要在每次通话中添加<SomeImplementation> ...

答案 2 :(得分:1)

  1. 虽然我知道你不想要它,但我认为你应该重新思考方法名称是否应该相同。我看不出同一个名字如何对一个实例和这些实例的集合起作用。例如,如果您的方法名称ShootT,那么另一种方法应该听起来像ShootThemAll或类似的东西。

  2. 否则你应该让你的作业略有不同:

    IEnumerable<SomeImplementation> instances = new List<SomeImplementation>();
    instances.Method(); //now this should work
    
  3. 作为最后一个选项,正如Dimitry在评论中所说,你必须明确指定类型参数。

    instances.Method<SomeImplementation>();