C#泛型:将泛型类型转换为值类型

rit*_*gig 13 c# generics casting bitconverter

我有一个泛型类,它保存指定类型T的值.值可以是int,uint,double或float.现在我想获取值的字节以将其编码为特定协议.因此,我想使用BitConverter.GetBytes()方法,但遗憾的是Bitconverter不支持泛型类型或未定义的对象.这就是为什么我想要转换值并调用GetBytes()的特定重载.我的问题:如何将通用值转换为int,double或float?这不起作用:

public class GenericClass<T>
    where T : struct
{
    T _value;

    public void SetValue(T value)
    {
        this._value = value;
    }

    public byte[] GetBytes()
    {
        //int x = (int)this._value;
        if(typeof(T) == typeof(int))
        {
            return BitConverter.GetBytes((int)this._value);
        }
        else if (typeof(T) == typeof(double))
        {
            return BitConverter.GetBytes((double)this._value);
        }
        else if (typeof(T) == typeof(float))
        {
            return BitConverter.GetBytes((float)this._value);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

是否有可能投射通用值?或者有另一种获取字节的方法吗?

Eri*_*ert 28

首先,这是一个非常糟糕的代码味道.每当你对类型参数进行类型测试时,这样的几率很好,你就是在滥用泛型.

C#编译器知道你以这种方式滥用泛型,并且不允许从类型T的值转换为int等等.在将它转换为int之前,可以通过将值转换为object来关闭编译器.

return BitConverter.GetBytes((int)(object)this._value);
Run Code Online (Sandbox Code Playgroud)

呸.同样,最好找到另一种方法来做到这一点.例如:

public class NumericValue
{
    double value;
    enum SerializationType { Int, UInt, Double, Float };
    SerializationType serializationType;        

    public void SetValue(int value)
    {
        this.value = value;
        this.serializationType = SerializationType.Int
    }
    ... etc ...

    public byte[] GetBytes()
    {
        switch(this.serializationType)
        {
            case SerializationType.Int:
                return BitConverter.GetBytes((int)this.value);
            ... etc ...
Run Code Online (Sandbox Code Playgroud)

没有必要的泛型.为实际上通用的情况保留泛型.如果您已经为每种类型编写了四次代码,那么您没有获得任何泛型.

  • @rittergig:考虑编写一个*immutable*矩阵来进行数学运算.当你向它添加3时,你不会将数字2变为5; 你创建一个全新的5号,2仍然存在.矩阵也是如此. (3认同)

Jon*_*eet 13

好吧,这让我觉得这个类型的开头不是通用的:它只能是少数类型中的一种,你不能表达这种约束.

然后你想GetBytes根据类型调用不同的重载T.泛型不适用于那种事情.在.NET 4及更高版本中,您可以使用动态类型来实现它:

public byte[] GetBytes()
{
    return BitConverter.GetBytes((dynamic) _value);
}
Run Code Online (Sandbox Code Playgroud)

...但是这并不是一个真正的设计.


Dej*_*avu 9

晚会迟到,但只是想评论评论说原始提案是一个“糟糕的设计” - 在我看来,原始提案(虽然它不起作用)根本“不一定”是一个糟糕的设计!

来自强大的 C++ (03/11/14) 背景以及对模板元编程的深刻理解,我在 C++11 中创建了一个类型泛型序列化库,代码重复最少(目标是拥有非重复代码,并且我相信我已经完成了 99%)。C++11 提供的编译时模板元编程工具虽然可能变得非常复杂,但有助于实现序列化库的真正类型泛型实现。

然而,非常不幸的是,当我想在 C# 中实现一个更简单的序列化框架时,我正好卡在了 OP 发布的问题上。在 C++ 中,模板类型 T 可以完全“转发”到使用站点,而 C# 泛型不会将实际编译时类型转发到使用站点 - 对泛型类型 T 的任何第二(或更多)级别引用都会使 T成为一种在实际使用站点根本不可用的独特类型,因此 GetBytes(T) 无法确定它应该调用特定类型的重载 - 更糟糕的是,C# 中甚至没有好的方法可以说:嘿,我知道 T是int,如果编译器不知道,“(int)T”是否使它成为int?

此外,与其责怪基于类型的 switch 有一种糟糕的设计味道——这是一个很大的误称,当人们在做一些基于类型的高级框架并且由于语言环境的无能而不得不求助于基于类型的 switch 时,没有真正的了解手头实际问题的约束,人们开始公然说基于类型的 switch 是一个糟糕的设计 - 对于大多数传统的 OOP 用例来说是这样,但也有特殊情况,大多数时候高级用例像我们在这里谈论的问题,这是必要的。

还值得一提的是,我实际上会责怪 BitConverter 类是以传统的、无能的方式设计来满足通用需求的:而不是针对“GetBytes”为每种类型定义特定于类型的方法,也许它会更通用友好地定义 GetBytes(T value) 的通用版本 - 可能有一些约束,因此用户通用类型 T 可以被转发并按预期工作,根本不需要任何类型切换!所有 ToBool/ToXxx 方法都是如此——如果 .NET 框架提供非通用版本的工具,人们如何期望通用框架尝试利用这个基础框架——类型切换或者如果没有类型切换,你结束为您尝试序列化的每种数据类型复制代码逻辑 - 哦,


Mic*_*haC 7

很晚的答案,但无论如何......有一种方法可以让它稍微好一点......以这种方式使用泛型:实现另一种为你转换类型的泛型类型。因此,您不必关心取消装箱,将类型转换为对象等......它会起作用。

此外,在您的 GenericClass 中,现在您不必切换类型,您可以使用IValueConverter<T>并转换它 as IValueConverter<T>。这样,泛型将为您找到正确的接口实现魔术,此外,如果 T 是您不支持的对象,则该对象将为空...

interface IValueConverter<T> where T : struct
{
    byte[] FromValue(T value);
}

class ValueConverter:
    IValueConverter<int>,
    IValueConverter<double>,
    IValueConverter<float>
{
    byte[] IValueConverter<int>.FromValue(int value)
    {
        return BitConverter.GetBytes(value);
    }

    byte[] IValueConverter<double>.FromValue(double value)
    {
        return BitConverter.GetBytes(value);
    }

    byte[] IValueConverter<float>.FromValue(float value)
    {
        return BitConverter.GetBytes(value);
    }
}

public class GenericClass<T> where T : struct
{
    T _value;

    IValueConverter<T> converter = new ValueConverter() as IValueConverter<T>;

    public void SetValue(T value)
    {
        this._value = value;
    }

    public byte[] GetBytes()
    {
        if (converter == null)
        {
            throw new InvalidOperationException("Unsuported type");
        }

        return converter.FromValue(this._value);
    }
}
Run Code Online (Sandbox Code Playgroud)