Explicit Casting Generic to Another Type in C#

I have the following code for C++, in a templated class that represents a point. I would like to translate it into C#:

template <class T>
class Point
{
    public:
        T x;
        T y;
        T z;

    template<typename U> explicit Point(const Point<U> &p)
       : x((T)p.x), y((T)p.y), z((T)p.z)
    {
    }
}

This code enables a point of a given type to be explicitly cast into a point of another type. For example, you may use it something like (admittedly I am not 100% sure on the syntax here, but I get the concept):

Point<float> p;
Point<int> q = (Point<int>)p;

How could I enable the equivalent to this in C#? So far I have:

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

    // Constructors exist that accept X, Y, and Z as parameters

    public static explicit operator Point<U>(Point<T> p)
    {

    }
}

This gives an error, however, saying "U" is undefined. This makes sense... but how/where do I define U? Is my approach incorrect?

The difference between my question and the one here is that I am simply changing the underlaying type of the generic class via a cast... not trying to change one generic class into a different generic class with the same underlaying type.

Upvotes: 4

Views: 4102

Answers (5)

Vahid
Vahid

Reputation: 5444

You cannot define additional generic type constraints, but you can do something like this, using operators and methods.

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

    public static explicit operator Point<T>(Point<int> v)
    {
        return v.As<T>();
    }

    public static explicit operator Point<T>(Point<double> v)
    {
        return v.As<T>();
    }

    public static explicit operator Point<T>(Point<float> v)
    {
        return v.As<T>();
    }

    public Point<TU> As<TU>()
    {
        return new Point<TU>()
        {
            X = ConvertTo<TU>(X),
            Y = ConvertTo<TU>(Y),
            Z = ConvertTo<TU>(Z)
        };
    }

    private static TU ConvertTo<TU>(T t)
    {
        return (TU) Convert.ChangeType(t, typeof(TU));
    }
}

Usage:

        Point<double> d = new Point<double>()
        {
            X = 10d, Y = 10d, Z = 10d
        };

        Point<int> i = (Point<int>) d;

        Point<float> f = (Point<float>) i;

        d = (Point<double>) f;

Upvotes: 1

kalimag
kalimag

Reputation: 1196

You cannot declare operators with additional generic type arguments, but you can declare ones to or from specific generic types like Point<int>. C# will also not let you perform arbitrary conversions by casting from or to T.

The least boilerplate heavy option that maintains a modicum of type safety would be to constrain the T parameter to IConvertible:

public class Point<T> where T : IConvertible
{
    // ...

    public static explicit operator Point<int>(Point<T> point)
    {
        // The IFormatProvider parameter has no effect on purely numeric conversions
        return new Point<int>(point.X.ToInt32(null), point.Y.ToInt32(null), point.Y.ToInt32(null));
    }    
}

However, this will not prevent users from declaring nonsensical, unsupported types such as Point<DateTime> which will then throw at runtime when attempting a conversion.

Upvotes: 1

Kit
Kit

Reputation: 21709

Similar to Kevin's answer, but without dynamic is to use a double cast:

public Point<U> To<U>()
{
    return new Point<U>((U)(object)X, (U)(object)Y, (U)(object)Z);
}

Both of our answers don't catch any issues at compile-time.

Upvotes: 3

Vlad
Vlad

Reputation: 35594

I think the best you can get is this:

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

    public Point<U> As<U>()
    {
        return new Point<U>()
        {
            X = Convert<U>(X),
            Y = Convert<U>(Y),
            Z = Convert<U>(Z)
        };
    }

    static U Convert<U>(T t) => (U)System.Convert.ChangeType(t, typeof(U));
}

You cannot define a generic conversion operator, so you need it to be an explicit function. Moreover, a simple cast (U)t won't work, so you need Convert.ChangeType (which will work if your types are numeric).

Usage:

var p1 = new Point<int> { X = 1, Y = 2, Z = 3 };
var p2 = p1.As<double>();

(works as expected).

Upvotes: 3

Kevin Gosse
Kevin Gosse

Reputation: 39007

As far as I know, this kind of generic cast is only allowed in C# if there's some kind of inheritance relationship between T and U.

The closest equivalent would be to define a generic method for the conversion:

public Point<U> To<U>()
{
    dynamic p = this;

    return new Point<U>((U)p.X, (U)p.Y, (U)p.Z);
}

You cannot convert directly T to U as the compiler has no way to know whether it'll be safe. I use the dynamic keyword to bypass that restriction.

Upvotes: 3

Related Questions