dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
18.78k stars 4k forks source link

Precompile compile-time declared Lambda Expression. #74178

Open Liero opened 1 month ago

Liero commented 1 month ago

Background and Motivation

Many UI Frameworks and libraries like Blazor, data grids, mappers, etc. required both

  1. LambdaExpression in order get reflection metadata such as PropertyInfo
  2. Func ekvivalent of the LambdaExpression (compiled lambda) in order to get actual value

For example:

<FormField Field="() => FirstName" />

or

mapper.Map(source => Source.FirstName, target => target.First_Name);

The library authors either go for runtime compilation (slow) or requires consumers to declare both, LambdaExpression and Func, although they look the same (DRY violation).

Proposed Change

A compiler should be able to optimize a compile time declared LambdaExpression so that the Compile() is fast. If lambda expression parameter types, return type and captured variables' types are known to the compiler, it should be possible to generate Func<>, shouldn't it?

There is actually no public API change needed.

A naive example

LambdaExpression<Func<string>> lambda = () => "Hello world!";

Roslyn will generate Func<string> equivalent and associate it with the lambda

Func<string> compiled_lambda = () => "Hello world!";
lambda.__compiled = compiled_lambda;

It's probably more difficult when there are captured variables, but some precompilation steps should be possible so that LambdaExpression.Compile() is fast.

Alternative Designs

A razor compiler actually generates both, LambdaExpression and Func for @bind directive. Consider more general approach, such as System.Runtime.CompilerServices attributes similar to [CallerMemberName]

void Foo<T>(LambdaExpression<Func<T>> expression, [CompileMember("expression")] Func<T> valueGetter = default!)
{
     string propertyName = FindPropertyName(expression);
     Console.WriteLine($"{propertyName}: {valueGetter()}");
}
var person = new Person { FirstName = "Daniel" }
foo(() => person.FirstName); //writes FirstName: Daniel
jjonescz commented 1 month ago

Sounds like that could be an external source generator, perhaps combined with an interceptor (to intercept the Compile() call)

jaredpar commented 1 month ago

A compiler should be able to optimize a compile time declared LambdaExpression so that the Compile() is fast.

The word should here though is rather vague. It's possible yes that it should be able to but what is the design that leads to us doing this?