如何以编程方式将循环转换为单个表达式?
How to programmatically convert a loop to a single expression?
我一直在读一本关于线性代数数学和编程微积分的书,有一点让我印象深刻的是代码的冗长。首先,我知道那是什么,性能比使用 for 循环要好得多,但是,这使得代码非常难以阅读。
有没有办法使用 for 循环来创建冗长的函数,那样代码更易于维护?
这是我所谈论的一个非常简单的例子:
public int Sum(int[] a, int[] b, int size)
{
var sum = 0;
for(var index = 0; index < size; index++)
{
sum += a[index] + b[index];
}
return sum;
}
现在假设 size
是 3。是否可以从方法 Sum
中生成一个表达式,如下所示:
public int Sum3(int[] a, int[] b)
{
return a[0] + b [0] + a[1] + b[1] + a[2] + b[2];
}
所以,实际上,方法Sum
并不是执行求和,而是用来创建一个可以求和的delegate
。它最终看起来像 Func<int[], int[], int> Sum3
.
目标是使用这种方法进行点积、行列式和矩阵乘法等运算。
更新:
我不想在一行代码中求和两个数组。那只是一个例子。我希望能够在不实际循环的情况下进行任何向量或矩阵运算。
也许这种方式适合你:
int[] arr1 = { 1, 2, 3, 4, 5 };
int[] arr2 = { 1, 2, 3, 4, 5 };
int index = 3;
var overallSum = arr1.Take(index).Sum() + arr2.Take(index).Sum();
如果您真的不想在最终运行时对数据进行迭代,我认为在编译期间不可能完成此操作,除非您始终知道数组的确切长度(或者您使 Int32.MaxValue
覆盖您的 sum
)。您必须在运行时使用 C# 代码编写一些脚本,然后执行它。
(这只是部分答案;它不涉及读取 Sum
方法的 IL。)
一旦你解析了原始函数(Sum
在这种情况下),你可以构造相应的 Sum3
函数如下:
// using static System.Linq.Expressions.Expression;
var a = Parameter(typeof(int[]));
var b = Parameter(typeof(int[]));
var expr1 = Lambda(
Add(
Add(
Add(
Add(
Add(
ArrayIndex(a, Constant(0)),
ArrayIndex(b, Constant(0))
),
ArrayIndex(a,Constant(1))
),
ArrayIndex(b, Constant(1))
),
ArrayIndex(a, Constant(2))
),
ArrayIndex(b, Constant(2))
),
a,
b
);
var fn = expr1.Compile();
fn.DynamicInvoke(new[] { 1, 2, 3 }, new[] { 4, 5, 6 });
当然,您可能希望根据 IL 中的内容自定义逻辑。
另请注意,这可能会降低性能,因为编译表达式树所需的时间通常至少比简单循环大一个数量级。除非您可以为 size
的各种值缓存已编译的委托,并且使用次数证明了这一点。
注意此代码的大部分来自我编写的 ExpressionTreeToString library,它可以生成创建给定表达式树所需的工厂方法调用。
// using ExpressionTreeToString;
Expression<Func<int[], int[], int>> expr = (a, b) => a[0] + b[0] + a[1] + b[1] + a[2] + b[2];
Console.WriteLine(expr.ToString("Factory methods"));
我一直在读一本关于线性代数数学和编程微积分的书,有一点让我印象深刻的是代码的冗长。首先,我知道那是什么,性能比使用 for 循环要好得多,但是,这使得代码非常难以阅读。
有没有办法使用 for 循环来创建冗长的函数,那样代码更易于维护?
这是我所谈论的一个非常简单的例子:
public int Sum(int[] a, int[] b, int size)
{
var sum = 0;
for(var index = 0; index < size; index++)
{
sum += a[index] + b[index];
}
return sum;
}
现在假设 size
是 3。是否可以从方法 Sum
中生成一个表达式,如下所示:
public int Sum3(int[] a, int[] b)
{
return a[0] + b [0] + a[1] + b[1] + a[2] + b[2];
}
所以,实际上,方法Sum
并不是执行求和,而是用来创建一个可以求和的delegate
。它最终看起来像 Func<int[], int[], int> Sum3
.
目标是使用这种方法进行点积、行列式和矩阵乘法等运算。
更新:
我不想在一行代码中求和两个数组。那只是一个例子。我希望能够在不实际循环的情况下进行任何向量或矩阵运算。
也许这种方式适合你:
int[] arr1 = { 1, 2, 3, 4, 5 };
int[] arr2 = { 1, 2, 3, 4, 5 };
int index = 3;
var overallSum = arr1.Take(index).Sum() + arr2.Take(index).Sum();
如果您真的不想在最终运行时对数据进行迭代,我认为在编译期间不可能完成此操作,除非您始终知道数组的确切长度(或者您使 Int32.MaxValue
覆盖您的 sum
)。您必须在运行时使用 C# 代码编写一些脚本,然后执行它。
(这只是部分答案;它不涉及读取 Sum
方法的 IL。)
一旦你解析了原始函数(Sum
在这种情况下),你可以构造相应的 Sum3
函数如下:
// using static System.Linq.Expressions.Expression;
var a = Parameter(typeof(int[]));
var b = Parameter(typeof(int[]));
var expr1 = Lambda(
Add(
Add(
Add(
Add(
Add(
ArrayIndex(a, Constant(0)),
ArrayIndex(b, Constant(0))
),
ArrayIndex(a,Constant(1))
),
ArrayIndex(b, Constant(1))
),
ArrayIndex(a, Constant(2))
),
ArrayIndex(b, Constant(2))
),
a,
b
);
var fn = expr1.Compile();
fn.DynamicInvoke(new[] { 1, 2, 3 }, new[] { 4, 5, 6 });
当然,您可能希望根据 IL 中的内容自定义逻辑。
另请注意,这可能会降低性能,因为编译表达式树所需的时间通常至少比简单循环大一个数量级。除非您可以为 size
的各种值缓存已编译的委托,并且使用次数证明了这一点。
注意此代码的大部分来自我编写的 ExpressionTreeToString library,它可以生成创建给定表达式树所需的工厂方法调用。
// using ExpressionTreeToString;
Expression<Func<int[], int[], int>> expr = (a, b) => a[0] + b[0] + a[1] + b[1] + a[2] + b[2];
Console.WriteLine(expr.ToString("Factory methods"));