Search code examples
c#expression-trees

How do I create one generic dot product method for various number types


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

Solution

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