Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there no way for Csharp to write a truly static λ expression?

Language : C# .Net Version : 8.0 IDE : VS2020 / VSCode OS : Windows11

Is there no way for Csharp to write a truly static λ expression?

I studied currying today and wrote a function :

using System.Linq.Expressions;
using Curryfy;

object Apply(Delegate _operator, object? arg1)
{
    Type t = _operator.GetType();
    if (!t.IsGenericType || t.GenericTypeArguments.Length < 2 || t.Name != "Func`" + t.GenericTypeArguments.Length)
        throw new Exception();

    var args = _operator.Method.GetParameters().Select(p => Expression.Variable(p.ParameterType)).ToArray();
    Expression expression = Expression.Call(_operator.Method, args);
    foreach (var arg in args.Reverse())
        expression = *Expression.Lambda(expression, arg);*
    Console.WriteLine(expression);
    return null;
}
***int sum(int a, int b) => a + b;***
System.Console.WriteLine(sum);
Apply(sum, 10);

It can run correctly and the results are as I expected :

System.Func`3[System.Int32,System.Int32,System.Int32]
Param_0 => Param_1 => <<Main>$>g__sum|0_1(Param_0, Param_1)

This sum function doesn't even use 'static'. But when I change this line to :

Func<int, int, int> sum = static (int a, int b) => a + b;

The Expression.Call(_operator.Method, args) throw a exception said :

Static method requires null instance, non-static method requires non-null instance.

What is the essential difference between these two ways of writing?

update at 14:34 1/11/2024:

Inspired by Michael Liu, I changed these implementation of sum to :

var a = Expression.Variable(typeof(int));
var b = Expression.Variable(typeof(int));
var aplusb = Expression.Add(a, b);
var sumExpression = Expression.Lambda(aplusb, a, b);
var sum = sumExpression.Compile();

Then Expression.Call(_operator.Method, args) didn't throw any exception.

like image 277
黎民禹 Avatar asked Sep 03 '25 17:09

黎民禹


1 Answers

What is the essential difference between these two ways of writing?

It looks like this difference in behavior is the result of a choice made by the current implementation of the C# compiler. When you write

int sum(int a, int b) => a + b;

the compiler generates a static method (even though you didn't mark it static), which works with the Expression.Call(MethodInfo, Expression[]) overload that you're using.

But when you write

Func<int, int, int> sum = static (int a, int b) => a + b;

the compiler actually generates an instance method, even though you explicitly marked it static! Apparently, this is for performance reasons (calling a static method via a delegate requires some extra argument shuffling compared to calling an instance method).

You can work around this behavior by updating your code to handle instance methods:

Expression expression = _operator.Method.IsStatic
    ? Expression.Call(_operator.Method, args)
    : Expression.Call(Expression.New(_operator.Method.DeclaringType), _operator.Method, args);

This will instantiate an instance of the compiler-generated class that holds the actual implementation of your sum method.

Caution: We're depending on some implementation details of the C# compiler here, which are subject to change. If you want to write code that's guaranteed to work forever, I recommend not passing lambda expressions to Apply.

like image 200
Michael Liu Avatar answered Sep 05 '25 06:09

Michael Liu