Language : C# .Net Version : 8.0 IDE : VS2020 / VSCode OS : Windows11
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?
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With