如何获取表达式参数的数组长度?向量点积函数实现疑问
问题
我需要实现一个方法,返回可计算两个同维度向量点积的函数,当前代码如下:
public static Func<T[], T[], T> GetVectorMultiplyFunction<T>() where T : struct { var x = Expression.Parameter(typeof(T[]), "x"); var y = Expression.Parameter(typeof(T[]), "y"); var body = Enumerable .Range(0, 3 /*这里想用Expression.ArrayLength(x)但无法直接使用*/) .Select(i => Expression.Multiply( Expression.ArrayIndex(x, Expression.Constant(i)), Expression.ArrayIndex(y, Expression.Constant(i)) )) .Aggregate(Expression.Add); var lambda = Expression.Lambda<Func<T[], T[], T>>(body, x, y); return lambda.Compile(); }
现在的问题是:我需要动态获取数组长度,但Expression.ArrayLength()返回的是UnaryExpression而非int类型,无法直接转换为int传入Enumerable.Range。有没有办法预先将数组长度求值为int?
更新:以下是该方法预期行为的单元测试:
[Test] public void GetVectorMultiplyFunctionReturnsFunctionForInt() { var first = new int[] { 1, 2, 3 }; var second = new int[] { 2, 2, 2 }; var expected = 1 * 2 + 2 * 2 + 3 * 2; var func = GetVectorMultiplyFunction<int>(); var actual = func(first, second); Assert.AreEqual(expected, actual); }
解决方案
核心矛盾在于:表达式树是编译阶段构建的,而数组长度只有运行时调用生成的函数时才能确定,因此不能用Enumerable.Range这种需要编译时确定循环次数的方式。正确做法是用表达式树构建运行时执行的循环逻辑。
修改后的完整代码:
public static Func<T[], T[], T> GetVectorMultiplyFunction<T>() where T : struct { var xParam = Expression.Parameter(typeof(T[]), "x"); var yParam = Expression.Parameter(typeof(T[]), "y"); // 检查向量维度是否匹配,不匹配抛出异常 var dimensionCheck = Expression.Condition( Expression.NotEqual(Expression.ArrayLength(xParam), Expression.ArrayLength(yParam)), Expression.Throw(Expression.New(typeof(ArgumentException).GetConstructor(new[] { typeof(string) }), Expression.Constant("两个向量维度必须相同"))), Expression.Empty() ); // 声明累加器变量,初始化为T的默认值 var accumulator = Expression.Variable(typeof(T), "accumulator"); var initAccumulator = Expression.Assign(accumulator, Expression.Default(typeof(T))); // 声明循环变量i,初始化为0 var loopVar = Expression.Variable(typeof(int), "i"); var initLoopVar = Expression.Assign(loopVar, Expression.Constant(0)); // 循环终止条件:i < 数组长度 var loopCondition = Expression.LessThan(loopVar, Expression.ArrayLength(xParam)); // 循环体:累加点积项,然后i自增 var loopBody = Expression.Block( Expression.Assign( accumulator, Expression.Add( accumulator, Expression.Multiply( Expression.ArrayIndex(xParam, loopVar), Expression.ArrayIndex(yParam, loopVar) ) ) ), Expression.PostIncrementAssign(loopVar) ); // 组合所有表达式块 var block = Expression.Block( new[] { accumulator, loopVar }, initAccumulator, initLoopVar, dimensionCheck, Expression.Loop(loopBody, loopCondition) ); // 编译为Lambda表达式 var lambda = Expression.Lambda<Func<T[], T[], T>>(block, xParam, yParam); return lambda.Compile(); }
关键说明
- 运行时循环:通过
Expression.Loop构建循环逻辑,循环条件和体均为表达式,数组长度在运行时动态获取,而非构建表达式树时确定。 - 维度校验:添加了向量长度相等的检查,避免非法输入导致的错误。
- 变量作用域:用
Expression.Variable声明累加器和循环变量,通过Expression.Block管理变量作用域,确保逻辑正确。
该实现支持任意长度的同维度向量点积计算,完全符合单元测试的预期。
内容的提问来源于stack exchange,提问作者kastrbl4nik
相关产品推荐
相关产品推荐

