csharplang/meetings/2021/LDM-2021-05-10.md
2021-05-10 15:45:59 -07:00

4.9 KiB

C# Language Design Meeting for May 10th, 2021

Agenda

  1. Lambda improvements

Quote of the Day

  • "I'll allow it"

Discussion

Lambda improvements

Lambda natural types in unconstrained scenarios

There are a few related open questions around how we handle lambda expressions and method groups in unconstrained scenarios. These are scenarios such as:

var a = (int x) => x; // What is the type of A?

void M1<T>(T t);
M1((int x) => x); // What is the type of T?

void M2(object o);
M2((int x) => x); // What is the type of the expression? ie, o.GetType() returns what?

These scenarios are all related to how much we want to define a "default" function type in C#, and how much we think doing so could stifle later development around value delegates (if we even do such a feature). In C# today, we have 3 function types:

  • Delegate types that inherit from System.Delegate.
  • Expression tree types that inherit from System.Linq.Expressions.**Expression.
  • Function pointer types.

All of these types support conversions from some subset of method groups or lambdas, and none is currently privileged above another. Overloads between these types are considered ambiguous, and users must explicitly include information that tells the compiler what type of function type to use, such as by giving a target type. If we allow var, conversions to object, and/or unconstrained generic type inference to work, we would be setting in stone what the "default" function type in C# is. This would be particularly noticeable if our future selves introduced a form of lightweight delegate types and wanted to make them the default in various forms of overload resolution. We are doing analogous work with interpolated strings currently, but interpolated strings are a much smaller section of the language than lambda expressions, and lambda irregularities are potentially much more noticeable.

We could protect our future selves here by making the spec more restrictive: Do not allow lambdas/method groups to be converted to unconstrained contexts. This would mean no var, no unconstrained type parameters, and no conversion to object. We would only infer when there was information that we could use to inform the compiler as to which type of function type to use: conversion to just System.Delegate would be fine, for example, because we know that the delegate type version was being chosen. While this would protect the ability to introduce a value delegate type later and make it the default, we see some potential usability concerns with making such a delegate type the default. At this point, we believe such a delegate type would be based on ref structs, and making var declare these types would be minefield for async and other existing scenarios. Using such types by default in generic inference would have similar issues around ref struct safety rules. And finally, if such a type were converted to object, it would by necessity need to be boxed somewhere, obviating the point of using a lightweight value delegate type in the first place. Given these concerns, we believe that we would just be protecting our ability to make the same decision later, and that another function type would not be a good "default".

Conclusion

We allow inferring a natural type for a lambda or method group in unconstrained scenarios, inferring Action/Func/synthesized delegate type, as appropriate.

Type inference

We went over the rules as specified in the proposal. The only missing bit is that, at final usage, a function type needs to look at constraints to determine whether it should be inferred to be a Delegate or an Expression.

Conclusion

Accepted, with the additional step around constraints.

Direct invocation

Finally today, we looked at supporting direct invocation of lambda expressions. This is somewhat related to the first topic, but could be implemented even if we chose to do the restrictive version of that issue because this feature would not require us to actually choose a final function type for the lambda expression. We could just emit it as a method and call it directly, without creating a delegate instance behind the scenes at all. However, we don't have an important driving scenario behind this feature: technically it could be used as a form of statement expression, but it doesn't feel like a good solution to that problem. The main thing we want to make sure works is regularity with other inferred contexts:

// If this works
var zeroF = (int x) => x;
var zero = zeroF(0);

// This should also work
var zero = ((int x) => x)(0);

// But this wouldn't work with var, so is it fine to have not work here?
var zero = (x => x)(0);
Conclusion

We generally support the idea, even the final example. It may take more work however, and thus may not make C# 10. We'll create a more formal specification for approval.