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

时间:2013-04-11 21:04:36

标签: 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);
        }
    }
}

是否有可能投射通用值? 或者是否有其他方法来获取字节?

8 个答案:

答案 0 :(得分:28)

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

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

return BitConverter.GetBytes((int)(object)this._value);

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

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 ...

不需要泛型。为实际泛型的情况保留泛型。如果您已经为每种类型编写了四次的代码,那么您就没有获得任何泛型。

答案 1 :(得分:13)

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

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

public byte[] GetBytes()
{
    return BitConverter.GetBytes((dynamic) _value);
}

......但是这并不是一个好的设计。

答案 2 :(得分:4)

很晚才回答,但无论如何......有一种方法可以让它更好...... 以这种方式使用泛型:实现另一个为您转换类型的泛型类型。因此,您不必关心对象的拆箱,转换等......它只会起作用。

此外,在您的GenericClass中,现在您不必切换类型,您可以使用IValueConverter<T>并将其投射 as IValueConverter<T>。通过这种方式,泛型将为您找到正确的接口实现带来魔力,此外,如果T是您不支持的对象,则该对象将为null ...

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

答案 3 :(得分:3)

您可以使用Convert.ToInt32(this._value)(int)((object)this._value)。但总的来说,如果您发现自己必须在通用方法中检查特定类型,那么您的设计就会出现问题。

在您的情况下,您可能应该考虑创建一个抽象基类,然后为您将要使用的类型派生类:

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

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

    public abstract byte[] GetBytes();
}

public class IntGenericClass: GenericClass<int>
{
    public override byte[] GetBytes()
    {
        return BitConverter.GetBytes(this._value);
    }
}

答案 4 :(得分:3)

如果您的唯一目标是将GetBytes方法添加到这些类型中,那么将它们添加为扩展方法是不是一个更好的解决方案:

public static class MyExtensions {
    public static byte[] GetBytes(this int value) {
        return BitConverter.GetBytes(value) ;
    }
    public static byte[] GetBytes(this uint value) {
        return BitConverter.GetBytes(value) ;
    }
    public static byte[] GetBytes(this double value) {
        return BitConverter.GetBytes(value) ;
    }
    public static byte[] GetBytes(this float value) {
        return BitConverter.GetBytes(value) ;
    }
}

如果你真的需要你的泛型类用于其他目的,那就像Eric提到的那样脏的“双重类型转换”提到你首先将值转换为对象。

答案 5 :(得分:2)

GenericClass<DateTime>会做什么?相反,似乎你有一组离散的类知道如何获取它们的字节,所以创建一个抽象的基类来完成所有常见的工作,然后制作3个具体的类,它覆盖一个方法来指定在它们:

public abstract class GenericClass<T>
{
    private T _value;

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

    public byte[] GetBytes()
    {
        return GetBytesInternal(_value);
    }

    protected abstract byte[] GetBytesInternal(T value);
}

public class IntClass : GenericClass<int>
{
    protected override byte[] GetBytesInternal(int value)
    {
        return BitConverter.GetBytes(value);
    }
}

public class DoubleClass : GenericClass<double>
{
    protected override byte[] GetBytesInternal(double value)
    {
        return BitConverter.GetBytes(value);
    }
}

public class FloatClass : GenericClass<float>
{
    protected override byte[] GetBytesInternal(float value)
    {
        return BitConverter.GetBytes(value);
    }
}

这不仅为您的三种已知类型提供了干净,强类型的实现,而且让其他任何人都可以继承Generic<T>,并提供GetBytes的适当实现。

答案 6 :(得分:2)

晚会,但只是想评论评论说原来的提案是一个糟糕的设计&#34; - 在我看来,最初的提案(虽然它不起作用)不是必然的#34;一个糟糕的设计!

来自强大的C ++(03/11/14)背景,深刻理解模板元编程,我在C ++ 11中创建了一个类型通用的序列化库,只需要很少的代码重复(目标是具有非重复性)代码,我相信我已经实现了99%)。 C ++ 11提供的编译时模板元编程工具虽然可能变得极其复杂,但却有助于实现序列化库的真正类型泛型实现。

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

此外,不是指责那种基于类型的交换机有一种糟糕的设计气味 - 这是一个很大的误称,每当人们做一些基于类型的高级框架时,由于语言环境的无能而不得不求助于基于类型的交换机在没有真正理解手头实际问题的限制的情况下,人们开始公然说基于类型的交换机是一个糟糕的设计 - 对于大多数传统的OOP使用情况而言,但是有特殊情况,大多数情况下是高级用法像我们在这里讨论的问题一样,这是必要的。

值得一提的是,我实际上会责怪BitConverter类是以传统且无能的方式设计的,以满足一般需求:而不是针对&#34; GetBytes&#34为每种类型定义特定于类型的方法;或许定义一个通用版本的GetBytes(T值)会更加通用友好 - 可能有一些约束,因此用户通用类型T可以转发并按预期工作而不需要任何类型切换!对于所有ToBool / ToXxx方法也是如此 - 如果.NET框架将设施提供为非通用版本,那么如何期望通用框架尝试使用此基础框架 - 类型切换或如果没有类型切换,则结束复制你试图序列化的每种数据类型的代码逻辑 - 哦,我想念我使用C ++ TMP的那一天,我只编写一次序列化逻辑,我可以支持几乎无限数量的类型。

答案 7 :(得分:0)

我支持这是一个有趣的现实生活问题,与“不良”设计无关,而与标准C#限制无关。通过object

进行投射
return BitConverter.GetBytes((int)(object)this._value);

或,以当前语言显示为

if (this._value is int intValue)
{
    return BitConverter.GetBytes(intValue);
} 

可以工作,但是由于ValueType装箱而导致性能下降。

此问题的解决方案是使用Unsafe.As<TFrom,TTo>()的NuGet软件包中的System.Runtime.CompilerServices.Unsafe

if(typeof(T) == typeof(int))
{
     return BitConverter.GetBytes(Unsafe.As<T, int>(ref this._value));
}

结果不会显式或隐式转换为object