Home > Software engineering >  How do I create one generic dot product method for various number types
How do I create one generic dot product method for various number types

Time:04-14

I have the following function:

public static Func<int[], int[], int> Foo()
    {
        Func<int[], int[], int> result = (first, second) => first.Zip(second, (x, y) => x * y).Sum();
        return result;
    }

I would like to create same Func but for various number types (long, short, etc. and not only int).

The code below does not work. I receive the following error (CS0019: Operator '*' cannot be applied to operands of type 'T' and '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;
        }

After some investigation I concluded that I need to generate code dynamically with expression trees, however, I could not find any useful resource on web. The ones I found only deal with very simple lambda expressions. I also tried to use reflection<> and ILSpy to peek into C#1 code automatically with the idea to manually change ints to Ts. However, it did not work - I think due to (RuntimeMethodHandle)/OpCode not supported: LdMemberToken/. Any Help would be appreciated. I am really interested in to solve this.

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

CodePudding user response:

Generic math is an upcoming feature that is in preview right now. So in the future, "static abstract interface members" are the way to handle this. If you opt in for preview features, you can write valid C# code like this:

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

It is still going to be some time before generic math releases and there are some unresolved problems with it (such as not being able to do "checked" math), so to actually answer your question, why not just use dynamic?

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

CodePudding user response:

If you are old school you can use Expressions to build up generic math

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

So any class that defines op_Addition and op_Multiplication, or the equivalent operators can be used with Operation<T>.Add and Operation<T>.Mul

Here System.Numerics.Vector2 defines the following operators, so you don't have to.

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);
}
  • Related