奇怪的扩展方法重载解析

时间:2016-07-25 22:10:31

标签: c# extension-methods overload-resolution

我无法让编译器解决扩展方法的正确重载问题。我解释的最好方法是使用一些代码。这是一个演示问题的LINQPad脚本。由于我遇到的问题,这将无法编译:

void Main(){
    new Container<A>().Foo(a=>false);
}

interface IMarker{}
class A : IMarker{
    public int AProp{get;set;}
}
class B : IMarker{
    public int BProp{get;set;}
}
class Container<T>{}

static class Extensions{
    public static void Foo<T>(this T t, Func<T, bool> func)
        where T : IMarker{
        string.Format("Foo({0}:IMarker)", typeof(T).Name).Dump();
    }
    public static void Foo<T>(this Container<T> t, Func<T, bool> func){
        string.Format("Foo(Container<{0}>)", typeof(T).Name).Dump();
    }
}

我得到的错误是:

  

以下方法或属性之间的调用不明确:“Extensions.Foo<Container<A>>(Container<A>, System.Func<Container<A>,bool>)”和“Extensions.Foo<A>(Container<A>, System.Func<A,bool>)

在我看来,它根本不含糊。第一种方法不接受Container<T>,只接受IMarker。似乎通用约束不能帮助解决重载,但在这个版本的代码中,它们看起来似乎是:

void Main(){
    new A().Bar();
    new A().Foo(a=>a.AProp == 0);
    new A().Foo(a=>false); // even this works
    new A().Foo(a=>{
        var x = a.AProp + 1;
        return false;
    });

    new Container<A>().Bar();
    new Container<A>().Foo(a=>a.AProp == 0);
    new Container<A>().Foo(a=>{
        var x = a.AProp + 1;
        return false;
    });
}

interface IMarker{}
class A : IMarker{
    public int AProp{get;set;}
}
class B : IMarker{
    public int BProp{get;set;}
}
class Container<T>{}

static class Extensions{
    public static void Foo<T>(this T t, Func<T, bool> func)
        where T : IMarker{
        string.Format("Foo({0}:IMarker)", typeof(T).Name).Dump();
    }
    public static void Foo<T>(this Container<T> t, Func<T, bool> func){
        string.Format("Foo(Container<{0}>)", typeof(T).Name).Dump();
    }

    public static void Bar<T>(this T t) where T : IMarker{
        string.Format("Bar({0}:IMarker)", typeof(T).Name).Dump();
    }
    public static void Bar<T>(this Container<T> t){
        string.Format("Bar(Container<{0}>)", typeof(T).Name).Dump();
    }
}

这会编译并产生预期结果:

  

酒吧(A:IMarker)
  FOO(A:IMarker)
  FOO(A:IMarker)
  FOO(A:IMarker)
  巴(集装箱&LT a取代;)
  FOO(容器&LT a取代;)
  FOO(容器&LT a取代;)

当我不在lambda表达式中引用lambda参数时,似乎只有一个问题,然后只有Container<T>类。当调用Bar时,没有lambda,它工作正常。当使用基于lambda参数的返回值调用Foo时,它可以正常工作。即使lambda的返回值与未编译的示例中的返回值相同,但lambda参数由虚拟赋值引用,它也可以工作。

为什么它在这些情况下有效但在第一种情况下不起作用?我做错了什么,或者我发现了编译器错误?我已经确认了C#4和C#6中的行为。

2 个答案:

答案 0 :(得分:5)

哦,我重新阅读了自己的答案后得到了它!好问题=) 重载不起作用,因为它在解析重载时不考虑约束where T:IMaker(约束不是方法签名的一部分)。当您在lambda中引用参数时,您可以向编译器添加提示:

  1. 这有效:

    new Container<A>().Foo(a => a.AProp == 0);
    

    因为在这里我们暗示a:A;

  2. 即使引用参数:

    ,这也不起作用
    new Container<A>().Foo(a => a != null);
    

    因为仍然没有足够的信息来推断出类型。

  3. 据我理解规范,在“Foo场景”中,推理可能在第二个(Func)参数上失败,从而使调用变得模糊。

    这是规范(25.6.4)所说的:

      

    类型推断作为方法调用(第14.5.5.1节)的编译时处理的一部分发生,并在调用的重载解析步骤之前发生。如果在方法调用中指定了特定方法组,并且未将任何类型参数指定为方法调用的一部分,则会将类型推断应用于方法组中的每个泛型方法。如果类型推断成功,则推断的类型参数用于确定后续重载解析的参数类型。

         

    如果重载解析选择泛型方法作为要调用的方法,则推断的类型参数将用作调用的运行时类型参数。如果特定方法的类型推断失败,则该方法不参与重载解析。类型推断的失败本身并不会导致编译时错误。但是,当重载解析无法找到任何适用的方法时,它通常会导致编译时错误。

    现在让我们来看看非常简单的“酒吧情景”。在类型推断之后,我们将获得仅一个方法,因为只有一个适用:

      {li> Bar(Container<A>) new Container<A>()(未实施IMaker)
    1. Bar(A)代表new A()(不是容器)
    2. 以下是ECMA-334 specification,以防万一。 附:我不是百分百肯定我做对了,但我更愿意认为我抓住了必要的部分。

答案 1 :(得分:1)

Sergey弄清楚为什么我想要做的事情不起作用,我想。这就是我决定做的事情:

void Main(){
    new A().Bar();
    new A().Foo(a=>a.AProp == 0);
    new A().Foo(a=>false);

    new Container<A>().Bar();
    new Container<A>().Foo(a=>a.AProp == 0);
    new Container<A>().Foo(a=>false); // yay, works now!
}

interface IMarker<T>{
    T Source{get;}
}

class A : IMarker<A>{
    public int AProp {get;set;}
    public A   Source{get{return this;}}
}
class B : IMarker<B>{
    public int BProp {get;set;}
    public B   Source{get{return this;}}
}

class Container<T> : IMarker<T>{
    public T Source{get;set;}
}

static class Extensions{
    public static void Foo<T>(this IMarker<T> t, Func<T, bool> func){}
    public static void Bar<T>(this IMarker<T> t){}
}

对我来说不幸的是,这对我的应用程序来说是一个很大的改变。但至少扩展层会更简单,最后它对于编译器和人类来说都不那么模糊,而且这是一件好事。