如何为各种数字类型创建一种通用点积方法
How do I create one generic dot product method for various number types
我有以下功能:
public static Func<int[], int[], int> Foo()
{
Func<int[], int[], int> result = (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
我想为各种数字类型(长、短等,而不仅仅是 int)创建相同的 Func。
下面的代码不起作用。我收到以下错误
(CS0019:运算符“*”不能应用于 'T' 和 'T' 类型的操作数):
public static Func<T[], T[], T> Foo<T>() where T : struct
{
Func<T[], T[], T> result = (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
经过一些调查,我得出结论,我需要用表达式树动态生成代码,但是,我在网上找不到任何有用的资源。我发现的那些只处理非常简单的 lambda 表达式。我还尝试使用 reflection<> 和 ILSpy 自动查看 C#1 代码,以手动将 int 更改为 Ts。但是,它不起作用 - 我认为是由于 (RuntimeMethodHandle)/OpCode 不受支持:LdMemberToken/。任何帮助,将不胜感激。我真的很想解决这个问题。
public static Expression<Func<int[], int[], int>> Foo()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(int[]), "first");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int[]), "second");
MethodInfo method = (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/);
Expression[] array = new Expression[1];
MethodInfo method2 = (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/);
Expression[] obj = new Expression[3] { parameterExpression, parameterExpression2, null };
ParameterExpression parameterExpression3 = Expression.Parameter(typeof(int), "x");
ParameterExpression parameterExpression4 = Expression.Parameter(typeof(int), "y");
obj[2] = Expression.Lambda<Func<int, int, int>>(Expression.Multiply(parameterExpression3, parameterExpression4), new ParameterExpression[2] { parameterExpression3, parameterExpression4 });
array[0] = Expression.Call(null, method2, obj);
return Expression.Lambda<Func<int[], int[], int>>(Expression.Call(null, method, array), new ParameterExpression[2] { parameterExpression, parameterExpression2 });
}
泛型数学是一项即将推出的功能,目前处于预览阶段。所以以后,“静态抽象接口成员”就是处理这个的方式。如果您选择使用预览功能,您可以像这样编写有效的 C# 代码:
public static Func<T[], T[], T> Foo<T>()
where T :
unmanaged,
IMultiplyOperators<T, T, T>,
IAdditiveIdentity<T, T>,
IAdditionOperators<T, T, T>
{
Func<T[], T[], T> result =
static (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
// generic sum doesn't exist yet in linq
public static T Sum<T>(this IEnumerable<T> source)
where T :
unmanaged,
IAdditionOperators<T, T, T>,
IAdditiveIdentity<T, T>
{
T sum = T.AdditiveIdentity;
foreach (var item in source)
{
sum += item;
}
return sum;
}
距离通用数学发布还需要一段时间,它还有一些未解决的问题(例如无法进行“检查”数学),所以要真正回答您的问题,为什么不直接使用动态?
public static Func<T[], T[], T> Foo<T>() where T : struct
{
Func<T[], T[], T> result = (first, second) => DynamicDotProduct(first.Zip(second));
return result;
}
private static T DynamicDotProduct<T>(IEnumerable<(T first, T second)> zipped) where T : struct
{
// here I am assuming default(T) is zero of that type
dynamic sum = default(T);
foreach((dynamic x, T y) in zipped)
{
sum += x * y;
}
return sum;
}
如果你是老派,你可以使用 Expressions
来建立通用数学
using System.Numerics;
internal class Program
{
static void Main(string[] args)
{
int[] i_a = { 1, 2, 3, 4 };
int[] i_b = { 7, 6, 5, 4 };
int i_dot = DotProduct(i_a, i_b);
// 50
float[] f_a = { 1f, 2f, 3f, 4f };
float[] f_b = { 7f, 6f, 5f, 4f };
float f_dot = DotProduct(f_a, f_b);
// 50f
Vector2[] v_a = { new Vector2(1, 2), new Vector2(3, 4) };
Vector2[] v_b = { new Vector2(7, 6), new Vector2(5, 4) };
Vector2 v_dot = DotProduct(v_a, v_b);
// [22f, 28f]
}
public static T DotProduct<T>(T[] left, T[] right)
{
if (left.Length==right.Length && left.Length>0)
{
// Use generic math defined in Operation<T>
T sum = Operation<T>.Mul(left[0], right[0]);
for (int i = 1; i < left.Length; i++)
{
sum = Operation<T>.Add(sum, Operation<T>.Mul(left[i], right[i]));
}
return sum;
}
return default(T);
}
}
public static class Operation<T>
{
static Operation()
{
var arg1 = Expression.Parameter(typeof(T));
var arg2 = Expression.Parameter(typeof(T));
Add = Expression.Lambda<Func<T, T, T>>(Expression.Add(arg1, arg2), arg1, arg2).Compile();
Mul = Expression.Lambda<Func<T, T, T>>(Expression.Multiply(arg1, arg2), arg1, arg2).Compile();
}
///<summary>Generic Addition</summary>
public static Func<T, T, T> Add { get; }
///<summary>Generic Multiplication</summary>
public static Func<T, T, T> Mul { get; }
}
因此任何定义 op_Addition
和 op_Multiplication
的 class 或等效运算符都可以与 Operation<T>.Add
和 Operation<T>.Mul
一起使用
这里System.Numerics.Vector2
定义了以下运算符,所以你不必。
public static Vector2 operator +(Vector2 left, Vector2 right)
{
return new Vector2(left.X + right.X, left.Y + right.Y);
}
public static Vector2 operator *(Vector2 left, Vector2 right)
{
return new Vector2(left.X * right.X, left.Y * right.Y);
}
我有以下功能:
public static Func<int[], int[], int> Foo()
{
Func<int[], int[], int> result = (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
我想为各种数字类型(长、短等,而不仅仅是 int)创建相同的 Func。
下面的代码不起作用。我收到以下错误 (CS0019:运算符“*”不能应用于 'T' 和 'T' 类型的操作数):
public static Func<T[], T[], T> Foo<T>() where T : struct
{
Func<T[], T[], T> result = (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
经过一些调查,我得出结论,我需要用表达式树动态生成代码,但是,我在网上找不到任何有用的资源。我发现的那些只处理非常简单的 lambda 表达式。我还尝试使用 reflection<> 和 ILSpy 自动查看 C#1 代码,以手动将 int 更改为 Ts。但是,它不起作用 - 我认为是由于 (RuntimeMethodHandle)/OpCode 不受支持:LdMemberToken/。任何帮助,将不胜感激。我真的很想解决这个问题。
public static Expression<Func<int[], int[], int>> Foo()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(int[]), "first");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int[]), "second");
MethodInfo method = (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/);
Expression[] array = new Expression[1];
MethodInfo method2 = (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/);
Expression[] obj = new Expression[3] { parameterExpression, parameterExpression2, null };
ParameterExpression parameterExpression3 = Expression.Parameter(typeof(int), "x");
ParameterExpression parameterExpression4 = Expression.Parameter(typeof(int), "y");
obj[2] = Expression.Lambda<Func<int, int, int>>(Expression.Multiply(parameterExpression3, parameterExpression4), new ParameterExpression[2] { parameterExpression3, parameterExpression4 });
array[0] = Expression.Call(null, method2, obj);
return Expression.Lambda<Func<int[], int[], int>>(Expression.Call(null, method, array), new ParameterExpression[2] { parameterExpression, parameterExpression2 });
}
泛型数学是一项即将推出的功能,目前处于预览阶段。所以以后,“静态抽象接口成员”就是处理这个的方式。如果您选择使用预览功能,您可以像这样编写有效的 C# 代码:
public static Func<T[], T[], T> Foo<T>()
where T :
unmanaged,
IMultiplyOperators<T, T, T>,
IAdditiveIdentity<T, T>,
IAdditionOperators<T, T, T>
{
Func<T[], T[], T> result =
static (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
// generic sum doesn't exist yet in linq
public static T Sum<T>(this IEnumerable<T> source)
where T :
unmanaged,
IAdditionOperators<T, T, T>,
IAdditiveIdentity<T, T>
{
T sum = T.AdditiveIdentity;
foreach (var item in source)
{
sum += item;
}
return sum;
}
距离通用数学发布还需要一段时间,它还有一些未解决的问题(例如无法进行“检查”数学),所以要真正回答您的问题,为什么不直接使用动态?
public static Func<T[], T[], T> Foo<T>() where T : struct
{
Func<T[], T[], T> result = (first, second) => DynamicDotProduct(first.Zip(second));
return result;
}
private static T DynamicDotProduct<T>(IEnumerable<(T first, T second)> zipped) where T : struct
{
// here I am assuming default(T) is zero of that type
dynamic sum = default(T);
foreach((dynamic x, T y) in zipped)
{
sum += x * y;
}
return sum;
}
如果你是老派,你可以使用 Expressions
来建立通用数学
using System.Numerics;
internal class Program
{
static void Main(string[] args)
{
int[] i_a = { 1, 2, 3, 4 };
int[] i_b = { 7, 6, 5, 4 };
int i_dot = DotProduct(i_a, i_b);
// 50
float[] f_a = { 1f, 2f, 3f, 4f };
float[] f_b = { 7f, 6f, 5f, 4f };
float f_dot = DotProduct(f_a, f_b);
// 50f
Vector2[] v_a = { new Vector2(1, 2), new Vector2(3, 4) };
Vector2[] v_b = { new Vector2(7, 6), new Vector2(5, 4) };
Vector2 v_dot = DotProduct(v_a, v_b);
// [22f, 28f]
}
public static T DotProduct<T>(T[] left, T[] right)
{
if (left.Length==right.Length && left.Length>0)
{
// Use generic math defined in Operation<T>
T sum = Operation<T>.Mul(left[0], right[0]);
for (int i = 1; i < left.Length; i++)
{
sum = Operation<T>.Add(sum, Operation<T>.Mul(left[i], right[i]));
}
return sum;
}
return default(T);
}
}
public static class Operation<T>
{
static Operation()
{
var arg1 = Expression.Parameter(typeof(T));
var arg2 = Expression.Parameter(typeof(T));
Add = Expression.Lambda<Func<T, T, T>>(Expression.Add(arg1, arg2), arg1, arg2).Compile();
Mul = Expression.Lambda<Func<T, T, T>>(Expression.Multiply(arg1, arg2), arg1, arg2).Compile();
}
///<summary>Generic Addition</summary>
public static Func<T, T, T> Add { get; }
///<summary>Generic Multiplication</summary>
public static Func<T, T, T> Mul { get; }
}
因此任何定义 op_Addition
和 op_Multiplication
的 class 或等效运算符都可以与 Operation<T>.Add
和 Operation<T>.Mul
这里System.Numerics.Vector2
定义了以下运算符,所以你不必。
public static Vector2 operator +(Vector2 left, Vector2 right)
{
return new Vector2(left.X + right.X, left.Y + right.Y);
}
public static Vector2 operator *(Vector2 left, Vector2 right)
{
return new Vector2(left.X * right.X, left.Y * right.Y);
}