为何表达式树允许类型匹配`is`表达式却禁止空值匹配?
is允许,空值匹配is null却报错? 先看第一段可正常运行的代码:
internal static class Program { private static void Main() { object value = "Hello"; Console.WriteLine(value.Test(o => o is string)); } private static bool Test<T>(this T value, Expression<Func<T, bool>> test) => test.Compile().Invoke(value); }
运行结果:
True
再对比这段触发编译错误的代码:
internal static class Program { private static void Main() { string? value = "Hello"; Console.WriteLine(value.Test(o => o is null)); // 编译错误 } private static bool Test<T>(this T value, Expression<Func<T, bool>> test) => test.Compile().Invoke(value); }
编译错误提示:
An expression tree cannot contain pattern-matching 'is' expression
为何后者触发上述编译错误,而前者同样使用is表达式却能正常运行?即为何类型匹配被允许,空值匹配却不行?
注:已知可以用Func<T, bool>替代Expression<Func<T, bool>>,但本次讨论不涉及该方案。
核心原因在于C#对表达式树支持的语法存在版本差异和规则区分:
o is string被编译器当作传统类型检查,而非模式匹配
在C# 7.0引入模式匹配语法之前,is就已经用于类型检查,表达式树对这种场景的支持是原生的——编译器会将o is string直接转换为Expression.TypeIs节点,这是表达式树很早就支持的基础节点类型,因此可以正常编译。o is null属于C# 7.0+引入的常量模式匹配,表达式树对其支持未覆盖is null是模式匹配中的常量模式,而表达式树对模式匹配的支持是逐步扩展的。早期版本的表达式树解析器无法处理这种模式匹配语法,编译器无法将其转换为对应的表达式树节点,因此抛出编译错误。两者的编译器处理逻辑完全不同
编译器对o is string的处理逻辑是类型检查,属于表达式树的原生支持范畴;而对o is null会识别为模式匹配表达式,由于表达式树未实现该模式的解析逻辑,导致报错。
简单总结:is string是表达式树原生支持的类型检查操作,而is null是后来引入的模式匹配语法,不在表达式树早期支持的范围内,因此出现编译差异。
内容的提问来源于stack exchange,提问作者Matthew Layton

