在泛型 C# 结构中使用运算符

本文关键字:运算符 结构 泛型 | 更新日期: 2023-09-27 18:34:04

我正在编写一个通用的C#结构。 我有以下几点:

struct GenericPoint<T> where T : IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T> {
  T x;
  T y;
  GenericPoint(T x, T y) {
    this.x = x;
    this.y = y;
  }
  static GenericPoint<T> operator +(GenericPoint<T> p1, GenericPoint<T> p2) {
    GenericPoint<T> r;
    r.x = (dynamic) p1.x + p2.x;
    r.y = (dynamic) p1.y + p2.y;
    return r;
  }

这将起作用,但是有更好的方法吗? 我的目的是 T 将是一个知道"+"运算符的类型。 我很高兴在编译时做出这个承诺。 但这可能吗?

在泛型 C# 结构中使用运算符

不幸的是,使用 C# 泛型无法做到这一点。

这就是为什么,例如,.NET框架提供了,PointPointFRectangleRectangleF等,其中这些结构的定义基本上是彼此重复的,用intfloat代替想要成为"泛型"的类型。


但是,如果你真的想这样做...

  1. 创建IMath<T>接口。(或者如果你喜欢冒险,甚至可以IField<T>。 请参阅此处:字段

    interface IMath<T>
    {
        T Zero { get; }
        T One { get; }
        T Negate(T a);
        T Add(T a, T b);
        T Sub(T a, T b);
        T Mult(T a, T b);
        //etc...
    }
    
  2. 修改GenericPoint<T>以具有对IMath<T>static引用。

    struct GenericPoint<T>
    {
        public static IMath<T> TMath { get; set; }
        public T x;
        public T y;
        public static GenericPoint<T> operator +(GenericPoint<T> p1, GenericPoint<T> p2)
        {
            GenericPoint<T> r;
            r.x = TMath.Add(p1.x, p2.x);
            r.y = TMath.Add(p1.y, p2.y);
            return r;
        }
    }
    
  3. 在"程序初始化"期间,为要支持的每种类型T设置GenericPoint<int>.TMathGenericPoint<float>.TMath等。如果您不小心尝试将GenericPoint<T>类与某种未指定IMath<T>的类型一起使用,则只会在使用运算符时得到NullReferenceException

你想做的事情你不能做(我希望能够做到)。 问题是在 C# 接口中不允许运算符重载。 因此,虽然你可以约束(http://msdn.microsoft.com/en-us/library/d5x73970.aspx)你的泛型变量('T'),如果你把它限制在一个接口上,它不会知道你有一个"+"运算符。 您可以使用适当的运算符重载定义基类,但随后必须将"T"限制为基于该类(因此不能使用任何值类型,如 int、double 等),或者不基于用作约束的对象的对象。 你的动态解决方案实际上非常好,最大的问题当然是你短路任何编译时警告,所以如果你使用不支持"+"的类,直到运行时失败你才会知道它。

有一个稍微简单的方法。使用 Linq.Expressions,可以创建将以泛型方式执行所需数学运算的委托,前提是如果类型不支持该操作,您愿意在运行时接受错误。这实际上更容易的唯一原因是它已经完成了,请参阅:http://www.yoda.arachsys.com/csharp/miscutil/

使用 Operator.cs 中定义的静态运算符类,可以将动态强制转换替换为:

r.x = (dynamic) p1.x + p2.x;
r.y = (dynamic) p1.y + p2.y;

成为:

r.x = Operator<T>.Add(p1.x, p2.x);
r.y = Operator<T>.Add(p1.y, p2.y);

C# 11/.NET 7(或更高版本)中的

struct GenericPoint<T> where T : Number<T>
{
  // as before, just remove the "dynamic"
}

您可以使用更受限制的接口,例如 IAdditionOperators<T,T,T> ,但INumber<T>可能会为您提供所需的一切以及更多。