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.