Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing generic arithmetic in C#

I have a list of numbers, and I wrote a method that performs some calculations on these numbers; all in all, it's about a page of code. The method performs some arithmetic and comparisons on these numbers.

My problem is that, in one case, the list is an IList<byte>, and in another case, it's an IList<float>. The algorithm in both cases is exactly the same (yes, I'm aware of things like overflow errors and loss of precision, but in my case it works). How can I write a method that will handle both lists ? I can't write something like void DoStuff<T>(IList<T> numbers), because there are no arithmetic operators (+ - * /) that are generic.

One solution is to simply store everything as float, but I'd like to avoid it. The lists are quite long, and thus storing floats instead of bytes would cost too much memory. I could also do something like DoStuffFloat(byteList.Select(b => (float)b)), but I don't want to pay the performance penalty, either, if I can avoid it.

Short of copy-pasting the entire method and replacing "float" with "byte" (or vice versa), is there some decent solution ?

EDIT: I should've mentioned that I'm restricted to using .NET 3.5 for this project.

like image 851
Bugmaster Avatar asked Dec 20 '22 16:12

Bugmaster


2 Answers

What you could do is create a generic interface that includes the operations that you want to support, create a generic factory to create instances for the supported types to perform the operations, and use it.

e.g.,

public interface IOperations<T>
{
    T Add(T a, T b);
    T Subtract(T a, T b);
    T Multiply(T a, T b);
    T Divide(T a, T b);
}

public static class Operations<T>
{
    public static IOperations<T> Default { get { return Create(); } }

    static IOperations<T> Create()
    {
        var type = typeof(T);
        switch (Type.GetTypeCode(type))
        {
        case TypeCode.Byte:
            return (IOperations<T>)new ByteOperations();
        case TypeCode.Single:
            return (IOperations<T>)new SingleOperations();
        default:
            var message = String.Format("Operations for type {0} is not supported.", type.Name);
            throw new NotSupportedException(message);
        }
    }

    class ByteOperations : IOperations<byte>
    {
        public byte Add(byte a, byte b)      { return unchecked ((byte)(a + b)); }
        public byte Subtract(byte a, byte b) { return unchecked ((byte)(a - b)); }
        public byte Multiply(byte a, byte b) { return unchecked ((byte)(a * b)); }
        public byte Divide(byte a, byte b)   { return unchecked ((byte)(a / b)); }
    }

    class SingleOperations : IOperations<float>
    {
        public float Add(float a, float b)      { return a + b; }
        public float Subtract(float a, float b) { return a - b; }
        public float Multiply(float a, float b) { return a * b; }
        public float Divide(float a, float b)   { return a / b; }
    }
}
T Mean<T>(IList<T> numbers)
{
    var operations = Operations<T>.Default;
    var sum = numbers.Aggregate(operations.Add);
    var count = (T)Convert.ChangeType(numbers.Count, typeof(T));
    return operations.Divide(sum, count);
}

var resultByte = Mean(new byte[] { 1, 2, 3, 4 });                // 2
var resultSingle = Mean(new float[] { 1.1F, 2.1F, 3.1F, 4.1F }); // 2.6F
var resultInt = Mean(new int[] { 1, 2, 3, 4 });                  // not supported

If you don't mind a small performance hit, you could dynamically create the operations needed.

class GenericOperations<T> : IOperations<T>
{
    public GenericOperations()
    {
        add = CreateLambda(Expression.Add);
        subtract = CreateLambda(Expression.Subtract);
        multiply = CreateLambda(Expression.Multiply);
        divide = CreateLambda(Expression.Divide);
    }
    private Func<T, T, T> add, subtract, multiply, divide;
    private static Func<T, T, T> CreateLambda(Func<Expression, Expression, BinaryExpression> op)
    {
        var a = Expression.Parameter(typeof(T), "a");
        var b = Expression.Parameter(typeof(T), "b");
        var body = op(a, b);
        var expr = Expression.Lambda<Func<T, T, T>>(body, a, b);
        return expr.Compile();
    }

    public T Add(T a, T b)      { return add(a, b); }
    public T Subtract(T a, T b) { return subtract(a, b); }
    public T Multiply(T a, T b) { return multiply(a, b); }
    public T Divide(T a, T b)   { return divide(a, b); }
}
like image 125
Jeff Mercado Avatar answered Mar 01 '23 11:03

Jeff Mercado


I don't know if this is the best method for your case but it is useful for similar cases too.

This can be done by using the dynamic keyword. What dynamic will do is it will not do the compile time checks until runtime.

Here is a small sample program to show how it works.

class Program
{
    static void Main()
    {
        List<byte> bytes = new List<byte>();
        bytes.Add(2);
        bytes.Add(1);

        List<float> floats = new List<float>();
        floats.Add(2.5F);
        floats.Add(1F);

        Console.WriteLine(DoStuff(bytes));
        Console.WriteLine(DoStuff(floats));
        Console.ReadLine();
    }

    static dynamic DoStuff(IList items)
    {
        dynamic item0 = items[0];
        dynamic item1 = items[1];
        return item0 - item1;
    }

}

Unfortunately in my quick testing I could not make IList<dynamic> work however using the non generic IList then accessing the members as a dynamic works fine.

like image 29
Scott Chamberlain Avatar answered Mar 01 '23 11:03

Scott Chamberlain