16

我有一个通用类,它保存指定类型 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);
        }
    }
}

是否有可能转换一个通用值?还是有另一种获取字节的方法?

4

8 回答 8

28

首先,这是一种非常糟糕的代码气味。任何时候你在像这样的类型参数上进行类型测试时,你都在滥用泛型。

C# 编译器知道您正在以这种方式滥用泛型,并且不允许将类型 T 的值强制转换为 int 等。您可以通过在将值强制转换为 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 ...

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

于 2013-04-11T21:11:38.360 回答
11

好吧,让我感到震惊的是,该类型一开始就不是适当的通用类型:它只能是少数类型之一,并且您无法表达该约束。

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

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

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

于 2013-04-11T21:09:30.970 回答
9

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

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

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

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

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

于 2016-07-10T06:19:43.500 回答
7

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

此外,在您的 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);
    }
}
于 2015-03-18T22:55:35.683 回答
3

您可能会使用Convert.ToInt32(this._value)or (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);
    }
}
于 2013-04-11T21:12:18.967 回答
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 提到的,您首先将值类型转换为对象。

于 2013-04-11T21:20:59.270 回答
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.

于 2013-04-12T21:29:29.433 回答
2

我站在一边,这是一个有趣的现实生活问题,与“糟糕”的设计无关,而是标准的 C# 限制。铸造通过object

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

或者,在当前语言中为

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

有效,但由于 ValueType 装箱而导致性能下降。

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

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

不会显式或隐式强制转换为object.

于 2020-10-30T08:24:50.067 回答