简体   繁体   中英

using operators in generic c# structs

I'm writing a generic C# struct. I have the following:

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

This will work, but is there a better way? My intention is that T will be a type that knows about the "+" operator. I'm happy to make that promise at compile time. But is that possible?

Unfortunately, there is no way to do this with C# generics.

This is why, for example, the .NET framework provides, Point and PointF , Rectangle and RectangleF , etc., where the definitions of those structs are basically duplicates of each other with int or float in place of the wanna-be-"generic" type.


But, if you really want to do this...

  1. Create a IMath<T> interface. (Or maybe even IField<T> if you're feeling adventurous. See here: Field )

     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. Modify GenericPoint<T> to have a static reference to a IMath<T> .

     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; rx = TMath.Add(p1.x, p2.x); ry = TMath.Add(p1.y, p2.y); return r; } } 
  3. During "program initialization," set GenericPoint<int>.TMath , GenericPoint<float>.TMath , etc., for every type T you want to support. If you ever accidentally try to use your GenericPoint<T> class with some type for which you didn't specify an IMath<T> , you'll just get a NullReferenceException at the time of using the operators.

What you want to do you can't (I'd like to be able to do it to). The problem is that in C# interface don't allow operator overloading. So while you can constrain ( http://msdn.microsoft.com/en-us/library/d5x73970.aspx ) your generic variable ('T') if you constrain it to a interface it won't know you have a '+' operator. You can define a base class with the appropriate operators overloaded but then you have to constrain 'T' to be based on that class (so you can't use any value types like int, double, etc.), or objects not based on the object used as your constraint. Your dynamic solution is actually pretty good, the big problem is of course you short circuit any compile time warnings so if you use a class that doesn't support '+' you won't know it until a runtime failure.

There is a slightly easier approach. Using Linq.Expressions it is possible to create delegates that will perform the desired math operations in a generic manner, assuming you're willing to accept errors at runtime if the type does not support the operation. The only reason this is actually easier is that it has already been done, see: http://www.yoda.arachsys.com/csharp/miscutil/

Using the static Operator class defined in Operator.cs, you may replace your dynamic casts with:

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

becomes:

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

in C# 11 / .NET 7 (or above):

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

You can use more restricted interfaces such as IAdditionOperators<T,T,T> , but INumber<T> probably gives you everything you want and more.

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