MaLiN2223
MaLiN2223

Reputation: 1299

C# generic operators - RTTI approach

I intend to ask about generic operator+ overloading but not in typical "can I do operator+ for generic type" way.

Questions are on the bottom

I recently started to create matrix class in C# and after a while It came to me that I cannot do simple T + T !

Thus, I googled and googled and found several workarounds.

  1. Create Expression link
  2. Create abstract class abstract class Matrix<T>{//some code}. Create 'protected virtual method Add(T itemToAdd)' and then create operator like this : T operator+(T item1, T item2){return item1.Add(item2);}(most posts on stack) and then inherit this method in class Matrix : Matrix<int> here
  3. Use method Add such as : T Add(T first, T second){ dynamic output = first + second; return output;} (somewhere on stack)

First one just does not suited me so I tried second one but then I run onto serious problems like:

  1. (A LOT of )repetative code - I created 4 classes for : int, double, long, Complex - my own type
  2. Creating multiple extension methods and so on.

Third one is just so unsafe that I rejected it immidietlay.

After my struggling I came to realise : 'Why don't I use RTTI and reflection?' I know, it is expensive in running time but why not use static constructor to do this?

Here is my idea (pseudocode):

class Matrix<T>{
   static Func<T,T,T> Add;
   static Matrix
   {
     if(T is int) 
        Add = (first,second) = > ((int)first)+((int)second);
     else if(T is long) 
        Add = (first,second) = > ((long)first)+((long)second);
   // and so on for built-in types
   else
   { // T is not built-in type
     if(typeof(T).GetMethods().Contains("op_Addition"))
     {
       Add = (first,second) => typeof(T).getMethod("op_Addition").invoke(first,second);
     } 
   }
}

I know that reflection is costly but it will do it only one time (per type)! And before you start argue : I am going to code T is int like this :

var type = typeof(T);
if(type==typeof(int)) // code

I am aware that I cannot explicitly convert T to int but there must be some sort of 'work around'. Problem is that (for example) Int32 has not explicit 'method' for operator+ hence, reflection is not of much use.

After all that introduction I have two questions :

  1. Is it a good approach or do you see major flaws in it?
  2. Is it doable? I don't want to start creating code without knowing for sure that my lambda function WILL work.

EDIT 1+2 I changed my code to generic. I figured that maybe you need an usage of my class, here you are :

Matrix<int> matrix = new Matrix(1,1); // creates int-based matrix
Matrix<MyClass> matrix2 = new Matrix(1,1); // creates some other type matrix

ANSWER based on dasblinkenlight's answer I managed to do this :

 public interface ITypeTratis<T>
    {
        T Add(T a, T b);
        T Mul(T a, T b);
        T Sub(T a, T b);
        T Div(T a, T b);
        bool Eq(T a, T b);
    }

    public class IntTypeTratis : ITypeTratis<int>
    {
        //code for int
    }
    public class DoubleTypeTratis : ITypeTratis<double>
    {
       //code for double
    }
internal class TypeTraits<T> : ITypeTratis<T>
{
    public Func<T, T, T> AddF;
    public Func<T, T, T> MulF;
    public Func<T, T, T> DivF;
    public Func<T, T, T> SubF;
    public Func<T, T, bool> EqF;
    public T Add(T a, T b) => AddF(a, b);

    public bool Eq(T a, T b) => EqF(a, b);

    public T Mul(T a, T b) => MulF(a, b);

    public T Sub(T a, T b) => SubF(a, b);

    public T Div(T a, T b) => DivF(a, b);
}
public class Matrix<T>
    { 
        private static IDictionary<Type, object> traitByType = new Dictionary<Type, object>()
        {
            {typeof (int), new IntTypeTratis()},
            {typeof (double), new DoubleTypeTratis()}
        };
        static Matrix()
        {
            Debug.WriteLine("Robie konstruktor dla " + typeof(T));
            var type = typeof(T);
            if (!traitByType.ContainsKey(type))
            {
                MethodInfo add, sub, mul, div, eq;
                if ((add = type.GetMethod("op_Addition")) == null)
                    throw new NotSupportedException("Addition is not implemented");
                if ((sub = type.GetMethod("op_Subtraction")) == null)
                    throw new NotSupportedException("Substraction is not implemented");
                if ((mul = type.GetMethod("op_Multiply")) == null)
                    throw new NotSupportedException("Multiply is not implemented");
                if ((div = type.GetMethod("op_Division")) == null)
                    throw new NotSupportedException("Division is not implemented");
                if ((eq = type.GetMethod("op_Equality")) == null)
                    throw new NotSupportedException("Equality is not implemented");
                var obj = new TypeTraits<T>
                {
                    AddF = (a, b) => (T)add.Invoke(null, new object[] { a, b }),
                    SubF = (a, b) => (T)sub.Invoke(null, new object[] { a, b }),
                    MulF = (a, b) => (T)mul.Invoke(null, new object[] { a, b }),
                    DivF = (a, b) => (T)div.Invoke(null, new object[] { a, b }),
                    EqF = (a, b) => (bool)eq.Invoke(null, new object[] { a, b })
                }; 
                traitByType[type] = obj;

            }
        }
}

And this is exactly what I was looking for.

Upvotes: 3

Views: 1438

Answers (5)

Marc Gravell
Marc Gravell

Reputation: 1062492

We can do this natively in C# 11 / .NET 7 (or above):

class Matrix<T> where T : INumber<T> // or just IAdditionOperators<T,T,T>
{
    T x, y, z; // just to show we can do things
    public T Sum() => x + y + z;
}

Upvotes: 1

Eduardo Wada
Eduardo Wada

Reputation: 2647

I think you are on the right path, in order to avoid using reflection, you are required to somehow inform the compiler that you know "T" has the "+" operator, however, this feature does not yet exist in C#, so this is impossible to implement without runtime type checking or imposing other constraints.

If you don't care about the performance, you could use dynamic:

(dynamic)first + (dynamic)second

but that will take several reflection performance hits in every operation

Or you could use some other more complex approach that caches the specific methods in a dictionary, but you won't escape calling at least .GetType() in your add's implementation

Upvotes: 0

Vilx-
Vilx-

Reputation: 106904

Bulding on dasblinkenlight's answer, here's my version of it. The benefit is that it doesn't need a dictionary lookup, instead making the type system do it. Should be faster, I think, but I haven't measured it. Also a bit less typing.

public abstract class MatrixBase
{
    protected static class OperationDict<T>
    {
        private static Func<T,T,T> _notSupported = (a, b) => { throw new NotSupportedException(string.Format("Type {0} not supported for Matrix operations!", typeof(T))); };

        public static Func<T, T, T> Add = _notSupported;
        public static Func<T, T, T> Multiply = _notSupported;
    }

    static MatrixBase()
    {
        OperationDict<int>.Add = (a, b) => a + b;
        OperationDict<int>.Multiply = (a, b) => a * b;

        OperationDict<decimal>.Add = (a, b) => a + b;
        OperationDict<decimal>.Multiply = (a, b) => a * b;

        // Etc. for all supported types

    }
}
public class Matrix<T> : MatrixBase
{
    public T DoAdd(T a, T b)
    {
        return OperationDict<T>.Add(a, b);
    }
}

Upvotes: 0

Joseph M. Shunia
Joseph M. Shunia

Reputation: 310

What is wrong with #3? You can just check for type, like so:

public abstract class Matrix<T>
{
    public static HashSet<Type> AllowAdd = new HashSet<Type>
    {
        typeof(int),
        typeof(long),
        typeof(string),
        typeof(double),
    };

    public T Add<T>(T first, T second)
    {
        if(!AllowAdd.Contains(typeof(T)))
        {
            throw new Exception(string.Format("Cannot preform addition for type: {0}", typeof(T).Name));
        }

        dynamic result = (dynamic)first + (dynamic)second;
        return (T)result;
    }
}

Upvotes: 0

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726479

Yes, your approach will work fine.

Your static constructor will run for each type parameter T, ensuring that Add is set correctly.

You may want to separate out the addition logic into a separate class outside your matrix, and use that class to run operations based on type for your matrix. For example, if you also need multiplication, you could build a ITypeTraits<T> interface that has Add and Multiply:

public interface ITypeTraits<T> {
    T Add(T a, T b);
    T Mul(T a, T b);
}

Now you can build implementations of ITypeTraits<T> for individual types, e.g.

public class IntTypeTraits : ITypeTraits<int> {
    public int Add(int a, int b) { return a+b; }
    public int Mul(int a, int b) { return a*b; }
}
public class LongTypeTraits : ITypeTraits<long> {
    public long Add(long a, long b) { return a+b; }
    public long Mul(long a, long b) { return a*b; }
}
... // and so on

make a dictionary out of them

static readonly IDictionary<Type,object> traitByType = new Dictionary<Type,object> {
    {typeof(int), new IntTypeTraits() }
,   {typeof(long), new LongTypeTraits() }
... // and so on
};

and get the one you need to perform operations:

ITypeTraits<T> traits = (ITypeTraits<T>)traitByType(typeof(T));
T first = ...
T second = ...
T sum = traits.Add(first, second);
T prod = traits.Mul(first, second);

Upvotes: 2

Related Questions