如何为各种数字类型创建一种通用点积方法

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_Additionop_Multiplication 的 class 或等效运算符都可以与 Operation<T>.AddOperation<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);
}