如何解释该“呼叫是模棱两可的"?错误? [英] How to explain this "call is ambiguous" error?
问题描述
考虑这两种扩展方法,它们只是从任何类型的 T1
到 T2
的简单映射,以及一个重载以流畅地映射 Task< T>
:
Consider these two extension methods which are just a simple map from any type T1
to T2
, plus an overload to fluently map over Task<T>
:
public static class Ext {
public static T2 Map<T1, T2>(this T1 x, Func<T1, T2> f)
=> f(x);
public static async Task<T2> Map<T1, T2>(this Task<T1> x, Func<T1, T2> f)
=> (await x).Map(f);
}
现在,当我使用第二个重载并映射到引用类型时...
Now, when I use the second overload with a mapping to a reference type...
var a = Task
.FromResult("foo")
.Map(x => $"hello {x}"); // ERROR
var b = Task
.FromResult(1)
.Map(x => x.ToString()); // ERROR
...我收到以下错误:
...I get the following error:
CS0121:以下方法或属性之间的调用不明确:"Ext.Map(T1,Func)"和"Ext.Map(Task,Func)"
CS0121: The call is ambiguous between the following methods or properties: 'Ext.Map(T1, Func)' and 'Ext.Map(Task, Func)'
映射到值类型可以很好地工作:
Mapping to a value type works fine:
var c = Task
.FromResult(1)
.Map(x => x + 1); // works
var d = Task
.FromResult("foo")
.Map(x => x.Length); // works
但是只有在映射实际上使用输入来产生输出的情况下,
But only as long the mapping actually uses the input to produce an output:
var e = Task
.FromResult(1)
.Map(_ => 0); // ERROR
问题
有人可以向我解释一下这是怎么回事吗?我已经放弃寻找解决此错误的可行方法,但至少我想了解造成这种混乱的根本原因.
The Question
Can anyone please explain to me what is going on here? I've already given up on finding a feasible fix for this error, but at least I'd like to understand the root cause of this mess.
到目前为止,不幸的是,在我的用例中,我发现了三种不可接受的解决方法.首先是明确指定 Task< T1> .Map< T1,T2>()
的类型参数:
So far I found three workarounds which are unfortunately not acceptable in my use case. The first is to specify the type arguments of Task<T1>.Map<T1,T2>()
explicitly:
var f = Task
.FromResult("foo")
.Map<string, string>(x => $"hello {x}"); // works
var g = Task
.FromResult(1)
.Map<int, int>(_ => 0); // works
另一种解决方法是不使用lambda:
Another workaround is to not use lambdas:
string foo(string x) => $"hello {x}";
var h = Task
.FromResult("foo")
.Map(foo); // works
第三个选择是将映射限制为内部功能(即 Func< T,T>
):
And the third option is to restrict the mappings to endofunctions (i.e. Func<T, T>
):
public static class Ext2 {
public static T Map2<T>(this T x, Func<T, T> f)
=> f(x);
public static async Task<T> Map2<T>(this Task<T> x, Func<T, T> f)
=> (await x).Map2(f);
}
我创建了.NET小提琴,您可以在其中亲自尝试上述所有示例.
I created a .NET Fiddle where you can try out all the above examples yourself.
推荐答案
According to C# Specification, Method invocations, the next rules are used to consider a generic method F
as a candidate for method invocation:
方法具有与类型实参列表中提供的方法类型参数相同的数量,
Method has the same number of method type parameters as were supplied in the type argument list,
和
一旦将类型参数替换为相应的方法类型参数,则参数列表中的所有构造类型 F
满足其约束条件(满足约束条件),并且 F
的参数列表适用于 A
(适用函数成员). A
-可选参数列表.
Once the type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of
F
satisfy their constraints (Satisfying constraints), and the
parameter list of F
is applicable with respect to A
(Applicable
function member). A
- optional argument list.
表达
Task.FromResult("foo").Map(x => $"hello {x}");
两种方法
public static T2 Map<T1, T2>(this T1 x, Func<T1, T2> f);
public static async Task<T2> Map<T1, T2>(this Task<T1> x, Func<T1, T2> f);
满足以下要求:
- 它们都有两个类型参数;
-
其构建的变体
- they both have two type parameters;
their constructed variants
// T2 Map<T1, T2>(this T1 x, Func<T1, T2> f)
string Ext.Map<Task<string>, string>(Task<string>, Func<Task<string>, string>);
// Task<T2> Map<T1, T2>(this Task<T1> x, Func<T1, T2> f)
Task<string> Ext.Map<string, string>(Task<string>, Func<string, string>);
令人满意的类型约束(因为 Map
方法没有类型约束)并且可以根据可选参数应用(因为 Map
方法也没有可选参数).注意:定义使用类型推断的第二个参数(lambda表达式)的类型.
satisfy type constraints (because there is no type constraints for Map
methods) and applicable according to optional arguments (because also there is no optional arguments for Map
methods). Note: to define the type of the second argument (lambda expression) a type inference is used.
因此,在此步骤中,算法将两种变体都视为方法调用的候选者.对于这种情况,它使用重载分辨率来确定哪个候选者更适合调用.规范中的字词:
So at this step the algorithm considers both variants as candidates for method invocation. For this case it uses Overload resolution to determine which candidate better fits for invocation. Words from specification:
使用以下方法确定一组候选方法中的最佳方法重载解析的重载解析规则.如果一个最好方法无法识别,方法调用不明确,并且发生绑定时间错误.执行过载解析时,代入方法后,将考虑通用方法的参数为相应的方法键入参数(提供或推断)类型参数.
The best method of the set of candidate methods is identified using the overload resolution rules of Overload resolution. If a single best method cannot be identified, the method invocation is ambiguous, and a binding time error occurs. When performing overload resolution, the parameters of a generic method are considered after substituting the type arguments (supplied or inferred) for the corresponding method type parameters.
表达
// I intentionally wrote it as static method invocation.
Ext.Map(Task.FromResult("foo"), x => $"hello {x}");
可以使用Map方法的构造变体用以下方式重写:
can be rewritten the next way using constructed variants of the method Map:
Ext.Map<Task<string>, string>(Task.FromResult("foo"), (Task<string> x) => $"hello {x}");
Ext.Map<string, string>(Task.FromResult("foo"), (string x) => $"hello {x}");
Overload resolution uses Better function member algorithm to define which of this two methods better fits method invocation.
我已经多次阅读了此算法,还没有找到算法可以定义方法 Exp.Map< T1,T2>(任务< T1> ;, Func< T1,T2>)的地方代码>作为考虑方法调用的更好方法.在这种情况下(当无法定义更好的方法时)会发生编译时错误.
I have read this algorithm several times and haven't found a place where the algorigthm can define the method Exp.Map<T1, T2>(Task<T1>, Func<T1, T2>)
as better method for considered method invocation. In this case (when better method cannot be defined) a compile time error occures.
总结:
- 方法调用算法将这两种方法都视为候选方法;
- 更好的函数成员算法无法定义更好的调用方法.
另一种帮助编译器选择更好方法的方法(就像您在其他解决方法中所做的那样):
Another approach of helping compiler to choose better method (as you did in your other workarounds):
// Call to: T2 Map<T1, T2>(this T1 x, Func<T1, T2> f);
var a = Task.FromResult("foo").Map( (string x) => $"hello {x}" );
// Call to: async Task<T2> Map<T1, T2>(this Task<T1> x, Func<T1, T2> f);
var b = Task.FromResult(1).Map( (Task<int> x) => x.ToString() );
现在,第一个类型参数 T1
已明确定义,并且不会出现歧义.
Now the first type argument T1
is explicitly defined and an ambiguity does not occur.
这篇关于如何解释该“呼叫是模棱两可的"?错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!