I wonder if there is a way, in C#, to have a type based on a primitive type, and duplicate it to use other primitives instead.
I know it's not very clear, I don't really know how to explain this (and english is not my mother-tongue, sorry for that...), so I'll try to explain it using pseudo-code.
A quick example:
public struct Vector2d {
public double X;
public double Y;
//Arithmetic methods: Length, normalize, rotate, operators overloads, etc...
}
The problem is that I'd like to have a float and a int32 version of this type.
What i'm doing actually (valid C# code):
//This type contains all useful methods
public struct Vector2d {
public float X;
public float Y;
public Vector2f AsFloat() {
return new Vector2f((float)X, (float)Y);
}
public Vector2f AsInteger() {
return new Vector2i((int)X, (int)Y);
}
//Arithmetic methods
}
//These types are only here to be casted from/to, all the arithmetics methods are on the double-based type
public struct Vector2f {
public float X;
public float Y;
public Vector2f AsDouble() {
return new Vector2d(X, Y);
}
public Vector2f AsInteger() {
return new Vector2i((int)X, (int)Y);
}
}
public struct Vector2i {
public int X;
public int Y;
public Vector2f AsFloat() {
return new Vector2f(X, Y);
}
public Vector2f AsDouble() {
return new Vector2d(X, Y);
}
}
It works but it's not "sexy", and in case of extensive casts using the AsXXX methods, there will inevitably be an impact on performances.
Ideally, I would have arithmetics methodes on all of three types, but it would be a pain to maintain...
What would be the ideal solution (pseudo-code, not valid C#):
public struct Vector2<T> where T : numeric {
public T X;
public T Y;
public T Length {
return (T)Math.Sqrt(X * X + Y * Y);
}
//Other arithmetic methods
}
I know this isn't possible in C# for the moment, but here is the true question:
Do you have any idea on how to nicely and efficiently handle this ?
What I've been thinking to (pseudo-code, not valid C#) :
//The TYPE defined constant should be used by Vector2Body instead of plain typed "double", "float", etc...
public struct Vector2d {
#define TYPE double
#import Vector2Body
}
public struct Vector2f {
#define TYPE float
#import Vector2Body
}
public struct Vector2i {
#define TYPE int
#import Vector2Body
}
That way, I wouldn't have duplicated code, easier to maintain
Waiting for your ideas :)
PS: If a moderator have ideas on how to better format my question, feel free to edit it :)
Here is a very nice approach given by @mike-z , using T4 templates:
The template (.tt file):
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
using System;
namespace My.Math {
<# Vector2Body("d", "double"); #>
<# Vector2Body("f", "float"); #>
<# Vector2Body("i", "int"); #>
}
<#+ private void Vector2Body(string suffix, string t) { #>
public struct Vector2<#= suffix #> {
public <#= t #> X;
public <#= t #> Y;
//Arithmetic for <#= t #>...
}
<#+ } #>
And the auto generated code:
using System;
namespace My.Math {
public struct Vector2d {
public double X;
public double Y;
//Arithmetic for double...
}
public struct Vector2f {
public float X;
public float Y;
//Arithmetic for float...
}
public struct Vector2i {
public int X;
public int Y;
//Arithmetic for int...
}
}
No generic, so no performance impact, code written once...
You could do something like,
public struct Vector2<T> where T : IConvertible
{
private readonly double x;
private readonly double y;
public Vector2(T x, T y)
{
this.x = x.ToDouble(CultureInfo.CurrentCulture);
this.y = y.ToDouble(CultureInfo.CurrentCulture);
}
public T X
{
get
{
return ConvertBack(this.x);
}
}
public T Y
{
get
{
return ConvertBack(this.y);
}
}
private static T ConvertBack(double d)
{
return (t)Convert.ChangeType(d, typeof(T), CultureInfo.CurrentCulture);
}
}
but, if you don't want generic treatment. Then you might as well have several specialized Vector2
types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With