在泛型 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# 泛型无法做到这一点。
这就是为什么,例如,.NET
框架提供了,Point
和PointF
,Rectangle
和RectangleF
等,其中这些结构的定义基本上是彼此重复的,用int
或float
代替想要成为"泛型"的类型。
但是,如果你真的想这样做...
-
创建
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... }
-
修改
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; } }
-
在"程序初始化"期间,为要支持的每种类型
T
设置GenericPoint<int>.TMath
、GenericPoint<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>
可能会为您提供所需的一切以及更多。