C# 泛型运算符 - RTTI 方法

本文关键字:RTTI 方法 运算符 泛型 | 更新日期: 2023-09-27 18:31:23

我打算询问泛型运算符+重载,但不是以典型的"我可以为泛型类型做运算符+吗"的方式。

问题在底部

我最近开始用 C# 创建矩阵类,过了一段时间,我发现我不能做简单的 T + T !

因此,我用谷歌搜索了又谷歌,发现了几种解决方法。

  1. 创建表达式链接
  2. 创建抽象类abstract class Matrix<T>{//some code}。创建"受保护的虚拟方法 Add(T itemToAdd)",然后创建像这样的运算符:T operator+(T item1, T item2){return item1.Add(item2);}(堆栈上的大多数帖子),然后在这里继承class Matrix : Matrix<int>
  3. 的方法
  4. 使用方法添加,例如:T Add(T first, T second){ dynamic output = first + second; return output;}(堆栈上的某个位置)

第一个不适合我,所以我尝试了第二个,但后来我遇到了严重的问题,例如:

  1. (很多)重复代码 - 我创建了 4 个类:int、double、long、Complex - 我自己的类型
  2. 创建多个扩展方法等。

第三个太不安全了,我拒绝了它。

在我挣扎之后,我意识到:"为什么我不使用RTTI和反射?我知道,它在运行时很昂贵,但为什么不使用静态构造函数来做到这一点呢?

这是我的想法(伪代码):

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

我知道反射是昂贵的,但它只会做一次(每种类型)!在你开始争论之前:我将像这样编写T is int代码:

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

我知道我不能明确地将 T 转换为 int,但必须有某种"解决方法"。问题是(例如)Int32 没有明确的运算符 + "方法",因此,反射没有多大用处。

在介绍完所有这些之后,我有两个问题:

  1. 这是一个好方法还是您认为其中存在重大缺陷?
  2. 可行吗?我不想在不确定我的 lambda 函数是否有效的情况下开始创建代码。

编辑 1+2我将代码更改为通用。我想也许你需要使用我的类,你在这里:

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

根据达斯布林肯莱特的回答,我设法做到了这一点:

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

这正是我想要的。

C# 泛型运算符 - RTTI 方法

是的,您的方法可以正常工作。

静态构造函数将针对每个类型参数运行 T ,确保正确设置Add

您可能希望将加法逻辑分离到矩阵外部的单独类中,并使用该类根据矩阵的类型运行操作。例如,如果你还需要乘法,你可以构建一个具有AddMultiplyITypeTraits<T>接口:

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

现在,您可以为单个类型构建ITypeTraits<T>的实现,例如

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

用它们制作字典

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

并获取执行操作所需的那个:

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

我们可以在 C# 11/.NET 7(或更高版本)中原生执行此操作:

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

#3 有什么问题? 您可以只检查类型,如下所示:

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的答案,这是我的版本。好处是它不需要字典查找,而是让类型系统执行此操作。我想应该更快,但我还没有测量过。打字也少了一点。

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

我认为您走在正确的道路上,为了避免使用反射,您需要以某种方式通知编译器您知道"T"具有"+"运算符,但是,此功能在 C# 中尚不存在,因此如果不进行运行时类型检查或施加其他约束,这是不可能实现的。

如果你不关心性能,你可以使用dynamic

(dynamic)first + (dynamic)second

但这会在每次操作中多次影响反射性能

或者你可以使用其他一些更复杂的方法来缓存字典中的特定方法,但你不会逃避在add的实现中调用至少.GetType()