奇怪的扩展方法重载决议

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

我得到的错误是:

The call is ambiguous between the following methods or properties: 'Extensions.Foo<Container<A>>(Container<A>, System.Func<Container<A>,bool>)' and '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();
    }
}

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

Bar(A:IMarker)
Foo(A:IMarker)
Foo(A:IMarker)
Foo(A:IMarker)
Bar(Container<A>)
Foo(Container<A>)
Foo(Container<A>)

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

为什么它在这些情况下有效,但在第一个情况下却无效?我做错了什么,还是发现了编译器错误?我已经确认了 C# 4 和 C# 6 中的行为。

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

  1. 这个有效:

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

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

  2. 即使引用参数也不起作用:

    new Container<A>().Foo(a => a != null);
    

    因为还没有足够的信息来推断类型。

据我了解规范,在 "Foo scenario" 中,推理可能会在第二个 (Func) 参数上失败,从而使调用不明确。

规范 (25.6.4) 的内容如下:

Type inference occurs as part of the compile-time processing of a method invocation (§14.5.5.1) and takes place before the overload resolution step of the invocation. When a particular method group is specified in a method invocation, and no type arguments are specified as part of the method invocation, type inference is applied to each generic method in the method group. If type inference succeeds, then the inferred type arguments are used to determine the types of arguments for subsequent overload resolution.

If overload resolution chooses a generic method as the one to invoke, then the inferred type arguments are used as the runtime type arguments for the invocation. If type inference for a particular method fails, that method does not participate in overload resolution. The failure of type inference, in and of itself, does not cause a compile-time error. However, it often leads to a compile-time error when overload resolution then fails to find any applicable methods.

现在让我们开始非常简单的 "Bar scenario"。类型推断后我们将得到只有一个方法,因为只有一个适用:

  1. Bar(Container<A>) for new Container<A>()(未实现 IMaker)
  2. Bar(A) 对于 new A()(不是容器)

这里是 ECMA-334 specification,以防万一。 P.s。我不是 100% 确定我做对了,但我更愿意认为我掌握了本质部分。

我认为

弄清楚了为什么我尝试做的事情不起作用。这是我决定改为做的事情:

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

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