简体   繁体   English

奇怪的扩展方法重载决议

[英]Weird extension method overload resolution

I'm having trouble getting the compiler to resolve the correct overload for an extension method. 我无法让编译器解决扩展方法的正确重载问题。 The best way for me to explain is with a little code. 我解释的最好方法是使用一些代码。 Here's a LINQPad script that demonstrates the problem. 这是一个演示问题的LINQPad脚本。 This won't compile because of the problem I'm having: 由于我遇到的问题,这将无法编译:

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

There error I get is: 我得到的错误是:

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>) ' 以下方法或属性之间的调用不明确:' Extensions.Foo<Container<A>>(Container<A>, System.Func<Container<A>,bool>) '和' Extensions.Foo<A>(Container<A>, System.Func<A,bool>) '

It seems to me that it's not ambiguous at all. 在我看来,它根本不含糊。 The first method won't accept a Container<T> , only an IMarker . 第一种方法不接受Container<T> ,只接受IMarker It seems like the generic constraints aren't assisting in overload resolution, but in this version of the code, they do seem to be: 似乎通用约束不能帮助解决重载,但在这个版本的代码中,它们似乎确实如下:

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

This compiles and produces the expected results: 这会编译并产生预期的结果:

Bar(A:IMarker) 酒吧(A:IMarker)
Foo(A:IMarker) FOO(A:IMarker)
Foo(A:IMarker) FOO(A:IMarker)
Foo(A:IMarker) FOO(A:IMarker)
Bar(Container<A>) 酒吧(集装箱<A>)
Foo(Container<A>) 美孚(集装箱<A>)
Foo(Container<A>) 美孚(集装箱<A>)

It seems to only have a problem when I don't reference the lambda parameter in the lambda expression, and then only with the Container<T> class. 当我不在lambda表达式中引用lambda参数,然后仅使用Container<T>类时,它似乎只有一个问题。 When calling Bar , there is no lambda, and it works fine. 当调用Bar ,没有lambda,它工作正常。 When calling Foo with the return value based on the lambda parameter, it works fine. 当使用基于lambda参数的返回值调用Foo时,它可以正常工作。 Even if the lambda's return value is the same as the one in the example that doesn't compile, but the lambda parameter is referenced by a dummy assignment, it works. 即使lambda的返回值与未编译的示例中的返回值相同,但lambda参数由虚拟赋值引用,它也可以工作。

Why does it work in these cases but not in the first? 为什么它在这些情况下有效而在第一种情况下不起作用? Am I doing something wrong, or have I found a compiler bug? 我做错了什么,或者我发现了编译器错误? I've confirmed the behavior in both C# 4 and C# 6. 我已经确认了C#4和C#6中的行为。

Oh, I got it after re-reading my own answer! 哦,我重新阅读了自己的答案后得到了它! Nice question =) The overload does not work because it does not take constraint where T:IMaker into account while resolving overload (constraint is not a part of method signature). 好的问题=)重载不起作用,因为在解决重载(约束不是方法签名的一部分)时where T:IMaker不考虑约束。 When you reference a parameter in lambda you (can) add a hint to the compiler: 当您在lambda中引用参数时,您(可以)向编译器添加提示:

  1. This works: 这有效:

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

    because here we do hint that a:A; 因为在这里我们暗示a:A;

  2. This does not work even with a reference to parameter: 即使引用参数,这也不起作用:

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

    because there is still not enough information to infer the type. 因为仍然没有足够的信息来推断出类型。

As far as I understand the specification, in "Foo scenario" the inference can fail on the second (Func) argument thus making the call ambiguous. 据我所知,在“Foo场景”中,推理可能会在第二个(Func)参数上失败,从而使调用变得模糊。

Here's what spec (25.6.4) says: 这是规范(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. 类型推断作为方法调用(第14.5.5.1节)的编译时处理的一部分发生,并在调用的重载解析步骤之前发生。 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. 但是,当重载解析无法找到任何适用的方法时,它通常会导致编译时错误。

Now lets get to pretty straightforward "Bar scenario". 现在让我们来看看非常简单的“酒吧情景”。 After type inference we will get only one method, because only one is applicable: 在类型推断之后,我们将获得一个方法,因为只有一个方法适用:

  1. Bar(Container<A>) for new Container<A>() (does not implement IMaker) Bar(Container<A>)用于new Container<A>() (未实现IMaker)
  2. Bar(A) for new A() (is not a Container) Bar(A) for new A() (不是Container)

And here's the ECMA-334 specification , just in case. 以下是ECMA-334规范 ,以防万一。 Ps I'm not 100% sure that I got it right, but I prefer to think that I grasped the essential part. Ps我不是100%肯定我做对了,但我更愿意认为我掌握了必不可少的部分。

Sergey figured out why what I was trying to do doesn't work, I think. 谢尔盖想出了为什么我想做的事情不起作用,我想。 This is what I've decided to do instead: 这就是我决定做的事情:

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

Unfortunately for me, this is a big change for my application. 对我来说不幸的是,这对我的应用来说是一个很大的改变。 But at least the extensions layer will be simpler, and in the end it will be less ambiguous for both the compiler and humans, and that's a good thing. 但至少扩展层会更简单,最终它对于编译器和人类来说都不那么模糊,这是一件好事。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM