Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the conversion parameter of Expression.Coalesce for?

For context on this question, please see the documentation for the Coalesce(Expression, Expression, LambdaExpression) overload of the Expression.Coalesce Method. I am referring specifically to the third parameter of this overload. I have a few questions about it for which I have been unable to find answers anywhere, including Microsoft's documentation:

  • Why would one choose to use this overload to supply a conversion?
  • How will it be used when compiling the expression?
  • How should the LambdaExpression one passes be constructed (I can only assume a specific parameter signature and return value type will be expected)?

I have repeatedly tried (by casting various lambda functions that employ the ?? operator in code to Expression<>) to get the C# compiler to compose an expression tree for me which makes use of this parameter. But every time I use the debugger to check the corollary property of the conversion parameter for the expression with NodeType Coalesce in the resulting tree, it is null.

I'm asking because I'm working on a library that works by analyzing expression trees and I need it to properly understand and support these conversions.

like image 842
Daniel Henry Avatar asked Dec 03 '18 06:12

Daniel Henry


1 Answers

You can figure out what the C# compiler does by looking at its source and the C# specification.

If you look at the code in the C# compiler that handles the coalesce expression for expression trees, you'll notice that it only uses conversion when the left sub-expression contains a user-defined expression.

You can then look at the section The null coalescing operator of the C# spec to see when that happens:

Specifically, a ?? b is processed as follows:

  • If A exists and is not a nullable type or a reference type, a compile-time error occurs.
  • […]
  • Otherwise, if b has a type B and an implicit conversion exists from a to B, the result type is B. At run-time, a is first evaluated. If a is not null, a is unwrapped to type A0 (if A exists and is nullable) and converted to type B, and this becomes the result. Otherwise, b is evaluated and becomes the result.
  • […]

So we need type A that has implicit user-defined conversion to B and use those two types in a null-coalescing expression:

class A
{
    public static implicit operator B(A s) => null;
}

class B {}

…

Expression<Func<B>> e = () => new A() ?? new B();

If you decompile this code, you'll see:

NewExpression left = Expression.New(typeof(A));
NewExpression right = Expression.New(typeof(B));
ParameterExpression parameterExpression = Expression.Parameter(typeof(A), "p");
UnaryExpression body = Expression.Convert(
    parameterExpression, typeof(B),
    (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/));
ParameterExpression[] obj = new ParameterExpression[1];
obj[0] = parameterExpression;
Expression.Lambda<Func<B>>(
    Expression.Coalesce(left, right, Expression.Lambda(body, obj)), Array.Empty<ParameterExpression>());

Replace GetMethodFromHandle with reflection code to get A.op_Implicit and you have code to create a valid Coalesce expression tree with non-null Conversion.

like image 199
svick Avatar answered Oct 19 '22 02:10

svick