I was about to bind Expression.Lambda
programmatically (because of unknown/variable type parameters), and found out that if the target method uses params
, the reflected calling is a bit different than calling directly.
First, in the official documentation, the "signature" (although this is more like a XML-doc):
public static
System.Linq.Expressions.Expression<TDelegate>
Lambda<TDelegate>(
System.Linq.Expressions.Expression body,
params System.Linq.Expressions.ParameterExpression[]? parameters
);
Notice that it the 2nd param is marked as optional, and we can write Expression.Lambda<T>(...)
. If in Visual Studio, you go to the disassembled declaration list, you can see this:
public static
Expression<TDelegate>
Lambda<TDelegate>(
Expression body,
params ParameterExpression[] parameters
);
2nd parameter is no longer marked as option. Now when I tried to invoke this method using reflection as:
var lambdaFactory = typeof(Expression)
.GetMethods()
// Filter overloads
.Single(x => x.ToString() == "System.Linq.Expressions.Expression`1[TDelegate] Lambda[TDelegate](System.Linq.Expressions.Expression, System.Linq.Expressions.ParameterExpression[])")
.MakeGenericMethod(myTypeArgument);
var lambda = (LambdaExpression) lambdaFactory.Invoke(
null,
new object[] {
...
// ,null
}
);
, I got TargetParameterCountException: Parameter count mismatch.
. But if null
is added as another parameter, it works perfectly.
This is a bit strange for me. Why does the MS Docs use ?
(optional marker)? Is the params
argument really optional similarly to regualr option arguments, like string Foo(int a = 1); var result = Foo();
? Or it is just a syntactic sugar? So that's why I may call directly Expression.Lambda<T>(...)
in the editor, but the compiled code can be different (which is compatible with the reflection system too). If so, does that mean the method always receives the null
value, even if I don't specify values? But if a method uses params
argument, and nothing is passed, the argument from the method body is a valid array with .Count == 0
, not null
. Is it safe to pass null
using reflection, or I should create an empty object array?
The ?
in the docs denote a nullable reference type, not an optional parameter. Nullable reference types are purely a "compile-time check" thing, and at runtime, they are no different from regular old reference types.:
Nullable reference types aren't new class types, but rather annotations on existing reference types. The compiler uses those annotations to help you find potential null reference errors in your code. There's no runtime difference between a non-nullable reference type and a nullable reference type. The compiler doesn't add any runtime checking for non-nullable reference types. The benefits are in the compile-time analysis. The compiler generates warnings that help you find and fix potential null errors in your code. You declare your intent, and the compiler warns you when your code violates that intent.
This explains why the ?
disappears when you look at the "disassembled declaration list".
All ?
tells you is that you can pass null
to this parameter and the method will still work.
In this case, an alternative to passing null is to pass an empty array of ParameterExpression
s:
var lambda = (LambdaExpression) lambdaFactory.Invoke(
null,
new object[] {
someBodyExpression,
new ParameterExpression[] { }
}
);
This would reflect how you call Lambda
non-reflectively:
// I'm passing no extra arguments, so an empty array is passed to the "params" parameter
Lambda<T>(someBodyExpression)