Bugmaster
Bugmaster

Reputation: 1080

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.

Upvotes: 4

Views: 2938

Answers (3)

Jeff Mercado
Jeff Mercado

Reputation: 134841

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

Upvotes: 6

djs
djs

Reputation: 230

Create classes to wrap the underlying values, and have them each implement an interface with the operations you need. Then, use ILists of that interface instead of the raw values.

Upvotes: 0

Scott Chamberlain
Scott Chamberlain

Reputation: 127563

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.

Upvotes: 5

Related Questions