如何通过表达式树高效获取Array类型数组的长度(用ldlen指令)
问题拆解
你遇到的核心困扰是:当表达式参数是抽象的Array类型(而非int[]这类具体数组)时,想跳过调用Length属性的繁琐操作,直接生成能使用ldlen指令的表达式来获取数组长度。从你的测试代码能清晰看到问题:
ParameterExpression para3 = Expression.Parameter(typeof(int[]), "p3"); ParameterExpression para4 = Expression.Parameter(typeof(Array), "p4"); MethodInfo mArrayLength = Strong.Instance<Array>.Property<int>(a => a.Length).GetGetMethod(); // 正常运行:针对具体数组类型int[] Expression asdf5 = Expression.ArrayLength(para3); // 无法运行:针对抽象Array类型 Expression asdf6 = Expression.ArrayLength(para4); // 正常运行:调用Array.Length的get方法 Expression asdf7 = Expression.Call(para4, mArrayLength);
asdf5能正常工作是因为它针对的是具体数组类型,但asdf6直接报错,只有调用属性get方法的asdf7能正常运行。你尝试过通过反射修改表达式字段来绕过限制,却触发了“操作可能破坏运行时”的异常。
为什么Expression.ArrayLength不支持Array类型?
这确实是表达式树库的设计限制:Expression.ArrayLength在构建阶段会严格校验参数类型,要求必须是具体的数组类型(比如int[]、string[]),而非抽象的Array基类。虽然底层IL指令ldlen确实可以直接处理Array实例,但表达式树API为了保持类型安全,特意做了这个校验限制。
反射修改表达式为啥行不通?
你尝试通过反射修改ArrayLengthExpression的操作数字段,虽然能绕开表达式树的构建校验,但编译后的委托运行时会触发异常——这是CLR的安全验证机制在起作用:它检测到ldlen指令的操作数是Array类型(而非具体数组),不符合IL的验证规则,所以抛出了“操作可能破坏运行时”的异常。这种方式属于绕过类型安全检查的未定义行为,绝对不能在生产环境使用。
靠谱的替代方案
其实不用纠结于直接调用ldlen,因为Array.Length属性的get方法在底层就是直接调用ldlen指令的(你可以反编译查看IL代码),所以Expression.Call(para4, mArrayLength)的性能和直接用ldlen几乎没有差别,完全可以放心使用。
如果真的非要生成使用ldlen的表达式,理论上可以把Array强制转换为object再转成某个具体数组类型,但这完全没必要,反而会引入额外的类型转换开销,得不偿失。
内容的提问来源于stack exchange,提问作者Nick

