2

I'm trying to make structure that represent four-dimensional vector.

I made something like this:

struct Vector4D<T> 
{
    public T v1;
    public T v2;
    public T v3;
    public T v4;

    //...

    public static Vector4D<T> operator *(Vector4D<T> a, T b)
    {
        a.v1 *= b;
        a.v2 *= b;
        a.v3 *= b;
        a.v4 *= b;
        return a;
    }
}

Well, this structure dosen't make sense if T isn't any numeric type like Int32, Int64, Double, Single, Decimal etc...

So, my question is how can I constrain T to be only one of following types, Int16, Int32, Int64, UInt16, UInt32, UInt64, Byte, SByte, Single, Double, Decimal?

I was trying do something like this

struct Vector4D<T> where T : Int16, Int32, Int64 // and so go on
{
    //....
}

But it didn't work.

HonzaT
  • 33
  • 2
  • 3
    http://stackoverflow.com/questions/32664/c-sharp-generic-constraint-for-only-integers – Prabhu Murthy May 05 '13 at 11:15
  • This seems to be the definitive anser for these questions: http://stackoverflow.com/questions/1348594/is-there-a-c-sharp-generic-constraint-for-real-number-types/1348625#1348625 – Pieter Geerkens May 05 '13 at 21:49

2 Answers2

1

You do have to explicitly write a multiply method for each type.

However, you can simplify things a bit as this compilable code sample shows:

using System;

namespace Demo
{
    internal class Program
    {
        static void Main()
        {
            var d = new Vector4D<double>{v1=1, v2=2, v3=3, v4=4};
            Console.WriteLine(d*2); // Prints 2, 4, 6, 8

            var i = new Vector4D<int>{v1=1, v2=2, v3=3, v4=4};
            Console.WriteLine(i*2); // Prints 2, 4, 6, 8

            // This will throw a "NotSupported" exception:
            var s = new Vector4D<string>{v1="1", v2="2", v3="3", v4="4"};
            Console.WriteLine(s*"");
        }
    }

    partial struct Vector4D<T>
    {
        public T v1;
        public T v2;
        public T v3;
        public T v4;

        public static Vector4D<T> operator *(Vector4D<T> a, T b)
        {
            a.v1 = multiply(a.v1, b);
            a.v2 = multiply(a.v2, b);
            a.v3 = multiply(a.v3, b);
            a.v4 = multiply(a.v4, b);
            return a;
        }

        public override string ToString()
        {
            return string.Format("v1: {0}, v2: {1}, v3: {2}, v4: {3}", v1, v2, v3, v4);
        }

        private static Func<T, T, T> multiply;
    }

    // Partial just to keep this logic separate.

    partial struct Vector4D<T>
    {
        static Vector4D() // Called only once for each T.
        {
            if (typeof(T) == typeof(int))
                Vector4D<int>.multiply = (a, b) => a*b;
            else if (typeof(T) == typeof(double))
                Vector4D<double>.multiply = (a, b) => a*b;
            else if (typeof(T) == typeof(float))
                Vector4D<float>.multiply = (a, b) => a*b;
            else
                multiply = (a, b) =>
                {
                    string message = string.Format("Vector4D<{0}> not supported.", typeof(T));
                    throw new NotSupportedException(message);
                };
        }
    }
}

That way, you can put all the multiply (and presumably divide, add and subtract) logic into the second partial struct and keep it all separate from the main logic.

The second partial struct only contains a static type constructor which will be called once only (per assembly domain) for each type T which is used to create a struct.

You do have the overhead of querying the type, but it is only once per run of the program and I guess the overhead would be pretty low.

Also, you don't have to use a partial struct at all - you can just put the static type constructor with the rest of the struct implementation. I only separated it out as an example, because it is purely initialisation logic which you could consider separately from the rest of the struct's logic.

Important Note that if you use the Vector4D with a type for which you haven't defined a multiply operation, you'll get the NotSupportedException defined in static Vector4D(). This does at least tell you exactly what is wrong, along the lines of:

Unhandled Exception: System.NotSupportedException: Vector4D<System.String> not supported.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • Your `initialised` check doesn't do anything in your alternative: for each `T`, `Vector4D`'s static constructor will be called at most once, and when it is called, `initialised` will be `false`. I'd go with your first suggestion, except add an `else` clause that does `Vector4D.multiply = (a, b) => thow new NotSupportedException();` –  May 05 '13 at 13:36
  • @hvd Ah thanks, I'll do that. – Matthew Watson May 05 '13 at 13:43
0

You cannot do this, not this way.

C# does not know anyhting about generic type T. Is it a number? Is it a string? Can you do math with it?

If you want to get this working, you have to use a generic calculator. You must build it yourself. For more info, take a look at: http://www.codeproject.com/Articles/8531/Using-generics-for-calculations

A simpler solution could be:

a.v1 = Convert.ChangeType(Convert.ToDecimal(a.v1) * Convert.ToDecimal(b), typeof(T));

EDIT

I created a few library functions on another location. You can use this to implement in your own code. Calculating with these numbers would be easy. Your Vector-class would be:

partial struct Vector4D<T>
where T: IComparable<T>, IEquatable<T>
{
    public Number<T> v1;
    public Number<T> v2;
    public Number<T> v3;
    public Number<T> v4;

    public static Vector4D<T> operator *(Vector4D<T> a, T b)
    {
        a.v1 *= b;
        a.v2 *= b;
        a.v3 *= b;
        a.v4 *= b;
        return a;
    }
}

See: https://codereview.stackexchange.com/questions/26022/improvement-requested-for-generic-calculator-and-generic-number

Community
  • 1
  • 1
Martin Mulder
  • 12,642
  • 3
  • 25
  • 54