4

Right now we have two structs to represent 2d points.

public struct Point2D
{
    public double X { get; set; }
    public double Y { get; set; }
}

public struct Point2DF
{
    public float X { get; set; }
    public float Y { get; set; }
}

Now we need to make another struct to represent 2d point for intergers.

public struct Point2DI
{
    public int X { get; set; }
    public int Y { get; set; }
}

My question is should I use generics here? If I use generics I will have one single struct instead of three.

public struct Point<T>
{
    public T X { get; set; }
    public T Y { get; set; }
}

But consumer can set T as string or some class/struct. What should I do? Is there any way I can force T to be double/int/float?

fhnaseer
  • 7,159
  • 16
  • 60
  • 112
  • 3
    Generics will not help you here in any way. You're better off with creating multiple types. – Sriram Sakthivel Jan 18 '16 at 07:11
  • As explained in that other question, you cannot make constraints to numeric types in C#. This is sadly not possible. You’ll either have to live with that (maybe adding run-time type checks), or stick with the individual types. – poke Jan 18 '16 at 07:12
  • 1
    C# *doesn't* have `Numeric` class (as *Java* does), so, alas, generic will not help. – Dmitry Bychenko Jan 18 '16 at 07:20

2 Answers2

6

I think its better to go for Generics, since this would be much cleaner given you don't need multiple classes, inheritance or any other fancy stuff (which would work too, but IMO it wouldn't be that clean)

Unfortunately there is no constraint for numeric types, so this is what I came up with which comes closest IMO:

public class Point<T>
    where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
{

    protected readonly Type[] AllowedTypes = 
        {
            typeof(decimal), typeof(double), typeof(short), typeof(int), typeof(long),
            typeof(sbyte), typeof(float), typeof(ushort), typeof(uint), typeof(ulong)
        };

    public Point()
    {   
        if (!this.AllowedTypes.Contains(typeof(T)))
        {
            throw new NotSupportedException(typeof(T).ToString());
        }           
    }

    public T X { get; set; }

    public T Y { get; set; }

    // UPDATE: arithmetic operations require dynamic proxy-Properties or
    // variables since T is not explicitly numeric... :(
    public T CalculateXMinusY()
    {
        dynamic x = this.X;
        dynamic y = this.Y;

        return x - y;
    }
}

It gives you max. IntelliSense-support while suggesting mainly valid input, but still fails when used inappropriately. All that using only one, clean, readable and therefore maintainable class.

The only thing thats kinda ugly about this, is the huge generic Type-Constraint, but this is what all numeric types in C# have in common so its better to do it that way.

nozzleman
  • 9,529
  • 4
  • 37
  • 58
  • I think we will not able to perform arithmetic operations on this. Also I will not be able to get extension methods for T (we have written some). I guess there is no answer for my question. – fhnaseer Jan 18 '16 at 10:19
  • That's true, since all my calculations happened inside the class, I assigned the required values (`X` and `Y` in your case), to `dynamic` variables, which enabled me to perform arithmetic calculations on it. Maybe this helps. I'll update my answer with an example of that. – nozzleman Jan 18 '16 at 10:24
  • updated my answer. if this is required more often, you should consider usig a protected property which does that "conversion". But doing math with this approach is possible (even though its not the most obvious kind of thing) – nozzleman Jan 18 '16 at 10:45
3

you can not create constraint for numeric types, most closest you can do is:

public struct Point<T> where T:struct, IComparable

as all numeric types implements IComparable and all primitive types are structs


UPDATE

Also, you can modify your architecture to simply create Points as follows

public abstract class Point
{
    protected Point()
    {

    }

    public static Point Create<T>(T x, T y)
    {
        if (typeof(T).IsAssignableFrom(typeof(int)))
            return new Point2DI { X = (int)(object)x, Y = (int)(object)y };

        if (typeof(T).IsAssignableFrom(typeof(double)))
            return new Point2D { X = (double)(object)x, Y = (double)(object)y };

        if (typeof(T).IsAssignableFrom(typeof(float)))
            return new Point2DF { X = (float)(object)x, Y = (float)(object)y };

        throw new Exception("Invalid type parameter");
    }
}

public class Point2D : Point
{
    public double X { get; set; }
    public double Y { get; set; }
}

public class Point2DF : Point
{
    public float X { get; set; }
    public float Y { get; set; }
}

public class Point2DI : Point
{
    public int X { get; set; }
    public int Y { get; set; }
}
Mikhail Tulubaev
  • 4,141
  • 19
  • 31