传递lambda函数在C#中的命名参数 [英] Passing lambda functions as named parameters in C#

查看:154
本文介绍了传递lambda函数在C#中的命名参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编译这个简单的程序:

 类节目
{
静态无效美孚(操作栏)
{
栏();
}

静态无效的主要(字串[] args)
{
美孚(()=> Console.WriteLine(42));
}
}



没什么好奇怪的存在。如果我们做了错误的lambda函数体:

 美孚(()=> Console.LineWrite(42) ); 



编译器返回一条错误消息:

 错误CS0117:'System.Console'不包含'LineWrite'


$的定义b $ b

到目前为止好。现在,让我们使用在调用一个名为参数

 美孚(巴:()=> Console.LineWrite(42)); 

这一次,编译器的消息有些混乱:

 错误CS1502:为在
'CA.Program.Foo(System.Action)的最佳重载的方法匹配具有一些无效参数
错误CS1503:参数1:无法从lambda表达式到System.Action'
转换

这是怎么回事?为什么它不报告的实际的错误?



请注意,我们做,如果我们使用匿名得到正确的错误消息方法,而不是拉姆达:

 美孚(条:委托{Console.LineWrite(42);}); 


解决方案

为什么不?它报告的实际误差




没有,这就是问题所在;它的的报告的实际错误。



让我稍微复杂一点的例子来说吧。假设你有这样的:

 类CustomerCollection 
{
公开的IEnumerable< R>选择< R>(Func键<客户,R>投影){...}
}
....
customers.Select((客户C)=> c.FristNmae) ;

确定,什么是错误的根据C#规范的?你必须非常仔细地阅读这里的规范。让我们来算一下。




  • 我们需要打个电话,选择与一个单一的参数,没有类型参数的函数调用。我们做了查找上选择在CustomerCollection,搜索名为选择可调用的东西 - 那就是,像委托类型或方法的领域。由于我们没有指定类型的参数,我们匹配任何通用的选择。我们找到一个和构建方法组出来。该方法组包含一个元素。


  • 方法组现在必须通过重载决议进行分析,以先确定的候选集的,然后从确定的适用候选集的,并从确定的最适用的候选的,并从确定的终于验证最佳适用候选人的。如果任何这些操作都失败了,重载必须失败,出现错误。这其中的一个不成?


  • 我们开始通过建立候选集。为了获得我们必须执行一个候选人的方法的类型推断的决定类型参数R的值如何方法的类型推断的工作?


  • 我们有一个lambda的参数类型都是已知 - 正规参数是客户。为了确定R,我们必须从lambda来R.的返回类型是什么拉姆达的返回类型进行映射?


  • 我们假设c是客户,并试图分析在lambda身体。这样做确实FristNmae的客户的情况下查找,并查找失败。


  • 因此​​,拉姆达返回类型推断失败,没有绑定添加至R


  • 在所有参数进行了分析上有R.方法类型推断没有界限因此无法确定R.类型


  • 因此方法类型推断失败。


  • 因此,没有方法被添加到候选集。


  • 因此,候选集是空的。


  • 因此不能有任何适用的人选。


  • 因此,正确的这里的错误信息会是这样的重载解析无法找到最后验证的最适用的选择,因为候选集是空的。




的客户会很不高兴的与错误讯息我们已经建立了相当数量的启发式进入,试图推断出更多的根本的错误,用户实际上可以采取行动来修复错误的错误报告algorith。我们的理由:




  • 实际的错误是,候选集是空的。为什么候选集是空的?


  • 由于有在方法组和类型推断只有一个方法失败。




OK,我们应该报告错误重载解析失败,因为方法的类型推断失败?再次,客户会不高兴这一点。相反,我们再次提出这样的问题:为什么没有方法的类型推断失败?




  • 因为绑定集的R是空的。



这是一个糟糕的错误了。为什么边界设置为空?




  • 因为从中我们可以决定R中的唯一参数是一个lambda的返回类型不能被推断。



OK,我们应该报告错误重载解析失败,因为拉姆达返回类型推断未能推断返回类型? 再次后,客户会不高兴这一点。相反,我们问这个问题:为什么在lambda无法推断返回类型?




  • 因为客户没有一个名为FristNmae成员。



的是我们居然报错。



所以你看推理的绝对曲折链我们必须为了给你想要的错误信息要经过。我们不能说什么地方出了错 - 这重载决议被赋予一个空的候选集 - 我们必须挖掘回到过去,以确定重载是如何陷入这种状态

这是这样做的代码的极其复杂的的;它比我刚才介绍,包括那里有n个不同的通用方法和M个不同的原因,类型推断失败的情况下更复杂的情况涉及,我们必须从所有他们的工作,什么是最好的理由放弃用户。回想一下,在现实中有十几种不同类型的选择和重载决议对所有的人都可能会失败的原因不同或相同的原因。



有错误启发式编译器处理各种重载解析故障的报告;我所描述的仅仅是其中之一。



所以,现在让我们来看看你的具体情况。什么是真正的错误?




  • 我们有在一个单一的方法,富的方法组。我们可以建立一个候选集?


  • 是的。有一位候选人。该方法foo是呼叫的候选人,因为它有每天的需要的提供的参数 - 巴 - 并且没有额外的参数


  • 确定,候选集有一个单一的方法。有没有候选集的成员适用


  • 没有。因为在lambda体内含有错误对应栏的参数不能转换为正式参数类型。


  • 因此​​适用的候选集是空的,因此不存在没有最后确认最佳人选适用的,因此重载解析失败。




因此,什么应该错误是什么?同样,我们不能只说重载决议未能找到最后验证的最佳候选者适用,因为客户会恨我们。我们必须开始挖的错误消息。为什么重载解析失败?




  • 由于适用的候选集是空的。



为什么是空的?




  • 由于它的每一个候选人被否决了。



有没有一个最佳人选?




  • 是的,有只有一名候选人。



为什么它被拒绝?




  • 因为它的参数是无法转换为正式参数类型。



确定,在这一点上显然启发式处理超载这涉及命名参数解析问题决定了我们挖远远不够的,这就是我们应该报告错误。如果我们没有命名的参数和其它一些启发式问:



为什么是说法不换股




  • 因为拉姆达体包含一个错误。



和我们再报告错误。



错误启发式并不完美;离得很远。巧合的是,我这个星期做简单的重载解析错误报告的启发式重rearchitecture - 就像在说的东西有没有那么花了2参数的方法时说:你要的方法是私有,当说有对应于该名称的参数,依此类推;这是完全可能的,你是调用方法有两个参数,有名字的两个参数没有公共的方法,还有一个是私有的,但其中一人有一个命名参数不匹配。快,我们应该报什么错误?我们必须作出最佳的猜测,有时候有一个更好的猜测,我们可以提出,但还不够精良之作。



即使获得该权利证明是一个非常棘手的工作。当我们最终得到重新架构大重型启发式扫描 - 例如如何应对LINQ表达式内的方法类型推断失败 - 我会重新考虑你的情况,看看我们是否能提高启发。



但因为你所得到的是错误信息是完全的正确的,这是不是在编译器的bug;相反,它仅仅是错误报告在特定情况下启发式的一个缺点。


Compile this simple program:

class Program
{
    static void Foo( Action bar )
    {
        bar();
    }

    static void Main( string[] args )
    {
        Foo( () => Console.WriteLine( "42" ) );
    }
}

Nothing strange there. If we make an error in the lambda function body:

Foo( () => Console.LineWrite( "42" ) );

the compiler returns an error message:

error CS0117: 'System.Console' does not contain a definition for 'LineWrite'

So far so good. Now, let's use a named parameter in the call to Foo:

Foo( bar: () => Console.LineWrite( "42" ) );

This time, the compiler messages are somewhat confusing:

error CS1502: The best overloaded method match for 
              'CA.Program.Foo(System.Action)' has some invalid arguments 
error CS1503: Argument 1: cannot convert from 'lambda expression' to 'System.Action'

What's going on? Why doesn't it report the actual error?

Note that we do get the correct error message if we use an anonymous method instead of the lambda:

Foo( bar: delegate { Console.LineWrite( "42" ); } );

解决方案

Why doesn't it report the actual error?

No, that's the problem; it is reporting the actual error.

Let me explain with a slightly more complicated example. Suppose you have this:

class CustomerCollection
{
    public IEnumerable<R> Select<R>(Func<Customer, R> projection) {...}
}
....
customers.Select( (Customer c)=>c.FristNmae );

OK, what is the error according to the C# specification? You have to read the specification very carefully here. Let's work it out.

  • We have a call to Select as a function call with a single argument and no type arguments. We do a lookup on Select in CustomerCollection, searching for invocable things named Select -- that is, things like fields of delegate type, or methods. Since we have no type arguments specified, we match on any generic method Select. We find one and build a method group out of it. The method group contains a single element.

  • The method group now must be analyzed by overload resolution to first determine the candidate set, and then from that determine the applicable candidate set, and from that determine the best applicable candidate, and from that determine the finally validated best applicable candidate. If any of those operations fail then overload resolution must fail with an error. Which one of them fails?

  • We start by building the candidate set. In order to get a candidate we must perform method type inference to determine the value of type argument R. How does method type inference work?

  • We have a lambda whose parameter types are all known -- the formal parameter is Customer. In order to determine R, we must make a mapping from the return type of the lambda to R. What is the return type of the lambda?

  • We assume that c is Customer and attempt to analyze the lambda body. Doing so does a lookup of FristNmae in the context of Customer, and the lookup fails.

  • Therefore, lambda return type inference fails and no bound is added to R.

  • After all the arguments are analyzed there are no bounds on R. Method type inference is therefore unable to determine a type for R.

  • Therefore method type inference fails.

  • Therefore no method is added to the candidate set.

  • Therefore, the candidate set is empty.

  • Therefore there can be no applicable candidates.

  • Therefore, the correct error message here would be something like "overload resolution was unable to find a finally-validated best applicable candidate because the candidate set was empty."

Customers would be very unhappy with that error message. We have built a considerable number of heuristics into the error reporting algorith that attempts to deduce the more "fundamental" error that the user could actually take action on to fix the error. We reason:

  • The actual error is that the candidate set was empty. Why was the candidate set empty?

  • Because there was only one method in the method group and type inference failed.

OK, should we report the error "overload resolution failed because method type inference failed"? Again, customers would be unhappy with that. Instead we again ask the question "why did method type inference fail?"

  • Because the bound set of R was empty.

That's a lousy error too. Why was the bounds set empty?

  • Because the only argument from which we could determine R was a lambda's whose return type could not be inferred.

OK, should we report the error "overload resolution failed because lambda return type inference failed to infer a return type"? Again, customers would be unhappy with that. Instead we ask the question "why did the lambda fail to infer a return type?"

  • Because Customer does not have a member named FristNmae.

And that is the error we actually report.

So you see the absolutely tortuous chain of reasoning we have to go through in order to give the error message that you want. We can't just say what went wrong -- that overload resolution was given an empty candidate set -- we have to dig back into the past to determine how overload resolution got into that state.

The code that does so is exceedingly complex; it deals with more complicated situations than the one I just presented, including cases where there are n different generic methods and type inference fails for m different reasons and we have to work out from among all of them what is the "best" reason to give the user. Recall that in reality there are a dozen different kinds of Select and overload resolution on all of them might fail for different reasons or the same reason.

There are heuristics in the error reporting of the compiler for dealing with all kinds of overload resolution failures; the one I described is just one of them.

So now let's look at your particular case. What is the real error?

  • We have a method group with a single method in it, Foo. Can we build a candidate set?

  • Yes. There is a candidate. The method Foo is a candidate for the call because it has every required parameter supplied -- bar -- and no extra parameters.

  • OK, the candidate set has a single method in it. Is there an applicable member of the candidate set?

  • No. The argument corresponding to bar cannot be converted to the formal parameter type because the lambda body contains an error.

  • Therefore the applicable candidate set is empty, and therefore there is no finally validated best applicable candidate, and therefore overload resolution fails.

So what should the error be? Again, we can't just say "overload resolution failed to find a finally validated best applicable candidate" because customers would hate us. We have to start digging for the error message. Why did overload resolution fail?

  • Because the applicable candidate set was empty.

Why was it empty?

  • Because every candidate in it was rejected.

Was there a best possible candidate?

  • Yes, there was only one candidate.

Why was it rejected?

  • Because its argument was not convertible to the formal parameter type.

OK, at this point apparently the heuristic that handles overload resolution problems that involve named arguments decides that we've dug far enough and that this is the error we should report. If we do not have named arguments then some other heuristic asks:

Why was the argument not convertible?

  • Because the lambda body contained an error.

And we then report that error.

The error heuristics are not perfect; far from it. Coincidentally I am this week doing a heavy rearchitecture of the "simple" overload resolution error reporting heuristics -- just stuff like when to say "there wasn't a method that took 2 parameters" and when to say "the method you want is private" and when to say "there's no parameter that corresponds to that name", and so on; it is entirely possible that you are calling a method with two arguments, there are no public methods of that name with two parameters, there is one that is private but one of them has a named argument that does not match. Quick, what error should we report? We have to make a best guess, and sometimes there is a better guess that we could have made but were not sophisticated enough to make.

Even getting that right is proving to be a very tricky job. When we eventually get to rearchitecting the big heavy duty heuristics -- like how to deal with failures of method type inference inside of LINQ expressions -- I'll revisit your case and see if we can improve the heuristic.

But since the error message you are getting is completely correct, this is not a bug in the compiler; rather, it is merely a shortcoming of the error reporting heuristic in a particular case.

这篇关于传递lambda函数在C#中的命名参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆