Why don't anonymous delegates/lambdas infer ty

2019-03-18 04:18发布

问题:

Several C# questions on StackOverflow ask how to make anonymous delegates/lambdas with out or ref parameters. See, for example:

  • Calling a method with ref or out parameters from an anonymous method
  • Write a lambda or anonymous function that accepts an out parameter

To do so, you just need to specify the type of the parameter, as in:

public void delegate D(out T p);
// ...
D a = (out T t) => { ... };      // Lambda syntax.
D b = delegate(out T t) { ... }; // Anonymous delegate syntax.

What I'm curious about is why the type is explicitly required. Is there a particular reason that this is the case? That is, from a compiler/language standpoint, why isn't the following allowed?

D a = (out t) => { ... };      // Lambda syntax -- implicit typing.
D b = delegate(out t) { ... }; // Anonymous delegate syntax -- implicit typing.

or even better, just:

D a = (t) => { ... };      // Lambda syntax -- implicit typing and ref|out-ness.
D b = delegate(t) { ... }; // Anonymous delegate syntax -- implicit typing and ref|out-ness.

回答1:

Interesting question.

First, consider the difference between anonymous methods and lambdas. From the compiler writer's perspective, the most important difference is that lambdas can require the compiler to infer the type of the parameters from the target to which the lambda is being assigned; C# 2 anonymous methods do not have this feature. This feature seems like a small difference but in fact it has major ramifications on the implementation of the compiler. See my blog series on this topic for some thoughts on why that is:

http://blogs.msdn.com/ericlippert/archive/2007/01/10/lambda-expressions-vs-anonymous-methods-part-one.aspx

So now let's come to your actual question: why can we not infer outness/refness from the target type to the parameters of the lambda. That is, if we have delegate void D(out int x) then surely D d = x=> { x = 10; } could infer that x is "out int".

There's no technical reason I'm aware of why we could not do that. Internally in the compiler the out/ref types are represented as types like any other.

However, features do not get done just because they can be done; they get done because there's a compelling reason to do so. For lambdas, the compelling reason to do type inference in the first place is LINQ; we want to be able to do a simple syntactic transformation on a query comprehension into a method call with lambdas, and let the method type inference engine work out the types of all the lambda parameters. None of the LINQ methods generated have delegates with out or ref parameters.

So, we have no compelling reason to do the feature. Delegates which have out/ref parameters are relatively rare. And assignment of lambdas to those delegates is rarer still. So this is a feature that we don't need, and that benefits almost no one.

C# 3 was the "long pole" on the Visual Studio schedule; we had the most number of days of work scheduled of any team that ships a component in VS. That meant that every day we slipped the schedule, the entire division slipped. That was a powerful disincentive to spending time on unnecessary features that benefitted no one. So the work was never done.

I agree that it would be nice to be more consistent here, but it's unlikely to happen. We have many higher priorities.



回答2:

From Eric Lippert's comment on why declaration and assignment of a var variable cannot be split:

I agree that in principle this could be done, but it is rather more complicated in practice than your quick sketch would indicate. var not only requires that there be an initializer, it also requires that the initializer not refer to the variable. If you have int M(out int) then you can say "int x = M(out x);" but you cannot say "var x = M(out x);" because to do overload resolution on M, we need to know the type of x, which is what we're attempting to figure out. Would it be legal to say "var s; if (b) M(out s); else s = 0;" ?

I'd guess the answer to your question is similar, considering, for example,

D a = (out var x) => x = M(out x);