【问题标题】:Generic extension method resolution fails通用扩展方法解析失败
【发布时间】:2014-04-22 11:00:16
【问题描述】:

下面的程序没有编译,因为在报错的那一行,编译器选择了带有单个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();
    }
  }
}

【问题讨论】:

  • 你试过instances.Method&lt;SomeImplementation&gt;();吗?
  • @Dmitry 确实可行。我会测试一些东西。

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


【解决方案1】:

方法解析表明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&lt;T&gt;)。这是为什么呢?

因为Method(T)在可以接受任何类型作为参数的意义上更接近,而且它不需要任何转换。

为什么Method(IEnumerable&lt;T&gt;) 不近?

因为变量的编译时类型为List&lt;T&gt;,所以需要将List&lt;T&gt;引用转换为IEnumerable&lt;T&gt;。哪个更接近但根本不进行任何转换。

回到你的问题。

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

同样,如前所述,要使用Method(IEnumerable&lt;T&gt;),我们需要一些引用转换,所以显然这并不接近。现在我们只剩下一种非常接近的方法是Method&lt;T&gt;。但问题是你已经用SomeInterface 限制了它,显然List&lt;SomeImplementation&gt;() 不能转换为SomeInterface

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

您可以通过将变量的静态类型更改为 IEnumerable&lt;SomeImplementation&gt; 来轻松修复它,这将起作用,现在您知道原因了。

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

【讨论】:

  • 好吧,这实际上并没有解决我的问题(我仍然需要给一个不同的名字,这样人们就可以毫无意外地使用它),但至少这是一个很好的解释 为什么.
【解决方案2】:

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

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

或者,正如 Dmitry 建议的那样,通过以下方式调用第二个:

instances.Method<SomeImplementation>();

但是这里你需要在每个呼叫中​​添加&lt;SomeImplementation&gt;...

【讨论】:

  • 这是个好主意。也许我应该扩展我的例子。在我们的实际项目中,T 受限于实现两个不同的接口,这就是我们使用泛型的原因。
  • 啊,好的。在那种情况下,我的想法不起作用(但适用于这种情况 - 刚刚测试过)......
  • 我尝试了很多东西,但在这两种情况下都无法让它与泛型一起使用,而无需额外的代码调用该方法,IMO 它应该可以工作 - 也许其他人可以对这种行为有所了解(或者它可能真的是方法解析中的一个错误)......
【解决方案3】:
  1. 虽然我知道您不想要它,但我认为您真的应该重新考虑方法名称是否应该相同。我看不到同名如何作用于实例以及此类实例的集合。例如,如果您的方法名称是 Shoot 对应于 T,那么另一个方法应该听起来像 ShootThemAll 或类似的名称。

  2. 否则你应该让你的任务略有不同:

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

    instances.Method<SomeImplementation>();
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-06
    • 1970-01-01
    • 2014-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多