简体   繁体   中英

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

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, eg

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

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

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM