在C#中将泛型显式转换为另一种类型

4

我有下面这段 C++ 代码,它是表示一个点的模板类。我希望将其翻译成 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)
    {
    }
}

这段代码可以将给定类型的点明确地转换为另一种类型的点。例如,您可以像以下方式使用它(尽管我对语法不是100%确定,但我理解概念):

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

我应该如何在C#中启用类似的功能?到目前为止,我有以下代码:
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)
    {

    }
}

这段代码出现了错误,提示“U”未定义。这是有道理的...但是我应该在哪里定义“U”呢?我的方法不正确吗?
这个问题的区别在于,我只是通过强制类型转换来改变泛型类的底层类型,而不是试图将一个泛型类转换为具有相同底层类型的不同泛型类。

转换运算符不能是通用的。 - TheGeneral
你需要将 U 作为通用参数吗?你可以用实际类型替换 U。当然,你只能进行有限数量的转换类型。 - gunr2171
5个回答

3
据我所知,在C#中只有在TU之间存在某种继承关系时,才允许进行这种通用转换。

最接近的等效方法是定义一个用于转换的通用方法:
public Point<U> To<U>()
{
    dynamic p = this;

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

你不能直接将T转换为U,因为编译器无法确定它是否安全。我使用dynamic关键字绕过这个限制。


这似乎更通用,因为它不需要数字类型。我的问题恰好是(只使用数字类型)...但如果情况不是这样的话,这是很好知道的。我想知道这种方法与Convert.ChangeType选项之间的性能差异是什么。 - Michael Kintscher they-them
1
@MichaelKintscher 这是一个好问题。我进行了快速基准测试,Convert.ChangeType 的执行时间为 127ns,而 Dynamic 的执行时间为 53ns。请注意,第一次调用 dynamic 更昂贵(因为表达式在第一次调用时被评估,然后被缓存)。 - Kevin Gosse
好的,我也在 TU 上都添加了 where U : IConvertible 的限制条件,只是为了保证我的类对消费者更加安全。 - Michael Kintscher they-them

3

我认为您可以得到的最好结果是这样的:

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));
}

你不能定义泛型转换运算符,因此需要它是一个显式函数。此外,简单的强制类型转换 (U)t 不起作用,所以需要使用 Convert.ChangeType(如果你的类型是数字类型,则会起作用)。

用法:

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

(正常工作).


1
请注意,Convert.ChangeType 仅适用于实现了 IConvertible 接口的类型。如果 OP 坚持使用基元类型,则完全没有问题。 - Kevin Gosse
2
@KevinGosse:是的,我的回答中说“如果你的类型是数字,这个方法会起作用”。感谢你的提示! - Vlad

3
Kevin的回答类似,但不使用dynamic,可以使用双重转换:
public Point<U> To<U>()
{
    return new Point<U>((U)(object)X, (U)(object)Y, (U)(object)Z);
}

我们两个答案都无法在编译时捕捉到任何问题。


这样行不通,你需要在转换之前取消装箱。int i = (int)(object)1.0f;会抛出InvalidCastException异常。 - Kevin Gosse
嗯,哦,你说得对。我在很多通用设计中都使用双重转换,但这总是针对引用类型的。 - Kit

1
你不能使用额外的泛型类型参数声明运算符,但可以声明特定泛型类型的运算符,例如 Point<int>。C# 也不允许通过从或向 T 进行强制转换来执行任意转换。
保持最少的样板代码并保持一定的类型安全性的选项是将 T 参数限制为 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));
    }    
}

然而,这并不能防止用户声明无意义、不支持的类型,比如 Point<DateTime>,在尝试进行转换时会在运行时抛出异常。

1

你无法定义额外的泛型类型约束,但是可以使用运算符和方法来实现类似的功能。

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));
    }
}

使用方法:

        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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接