您可以通过查看其源代码和 C# 规范来了解 C# 编译器的作用。
如果你看C# 编译器中处理表达式树的合并表达式的代码 https://github.com/dotnet/roslyn/blob/5a95c409/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/ExpressionLambdaRewriter.cs#L782-L795,你会注意到它只使用conversion
当左子表达式包含用户定义的表达式时。
然后你可以看看在该部分空合并运算符C# 规范 https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#the-null-coalescing-operator看看什么时候会发生:
具体来说,a ?? b
处理如下:
- If
A
存在并且不是可为 null 的类型或引用类型,则会发生编译时错误。
- […]
- 否则,如果
b
有一个类型B
并且存在隐式转换a
to B
,结果类型为B
。在运行时,a
首先被评估。如果a
不为空,a
展开以输入A0
(if A
存在且可为空)并转换为类型B
,这就是结果。否则,b
被评估并成为结果。
- […]
所以我们需要类型A
具有隐式用户定义的转换B
并在空合并表达式中使用这两种类型:
class A
{
public static implicit operator B(A s) => null;
}
class B {}
…
Expression<Func<B>> e = () => new A() ?? new B();
If you 反编译这段代码,你会看到 https://sharplab.io/#v2:D4AQDABCCMDcCwAocVoDoAyBLAdgRzQFEAPABwCcBTAZ2qwHsdqFEkQAmVAdiQG8kIgqABYIAWQCGuABQBKAUP6IhKiCQo06jADwgArNoBCAPmMRKEALwQ5VszkoB3CAEFbAfncQHzw3JYqAL5IwazInC58CoIgAMyoAGwQWAC2pAA2WADGWAAuEPSklOQSufTkEH4uENSydt4ArunpLKFsnIYQvIFAA:
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
用反射代码得到A.op_Implicit
并且您有代码来创建有效的Coalesce
非空表达式树Conversion
.