You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取表达式参数的数组长度?向量点积函数实现疑问

问题

我需要实现一个方法,返回可计算两个同维度向量点积的函数,当前代码如下:

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();
}

关键说明

  1. 运行时循环:通过Expression.Loop构建循环逻辑,循环条件和体均为表达式,数组长度在运行时动态获取,而非构建表达式树时确定。
  2. 维度校验:添加了向量长度相等的检查,避免非法输入导致的错误。
  3. 变量作用域:用Expression.Variable声明累加器和循环变量,通过Expression.Block管理变量作用域,确保逻辑正确。

该实现支持任意长度的同维度向量点积计算,完全符合单元测试的预期。


内容的提问来源于stack exchange,提问作者kastrbl4nik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 04:10:29