繁体   English   中英

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

[英]C# generics: cast generic type to value type

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

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

首先,这是一个非常糟糕的代码味道。 任何时候您对类型参数进行类型测试时,这种可能性都是很好的,您就是在滥用泛型。

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

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

不需要泛型。 储备仿制药是实际上通用的情况。 如果您为每种类型编写了四次代码,那么您对泛型没有任何好处。

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

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

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

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

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

来自强大的 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 框架提供非通用版本的工具,人们如何期望通用框架尝试利用这个基础框架——类型切换或者如果没有类型切换,你结束为您尝试序列化的每种数据类型复制代码逻辑 - 哦,我怀念我使用 C++ TMP 的那一天,我只为我可以支持的几乎无限数量的类型编写了一次序列化逻辑。

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

此外,在您的 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);
    }
}

您可能会使用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);
    }
}

如果您的唯一目标是将 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) ;
    }
}

如果你真的需要你的泛型类用于其他目的,只需像埃里克提到的那样首先将值类型转换为对象,就可以进行肮脏的“双重类型转换”。

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的适当实现的GetBytes

我认为这是一个有趣的现实生活问题,与“糟糕”的设计无关,而是与标准的 C# 限制有关。 通过object投射

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

或者,在当前语言中

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

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

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

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

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

此处建议的方法以及其他地方发现的其他方法的性能测试:

注意:[MethodImpl(MethodImplOptions.AggressiveInlining)] 提供非常小但可测量的性能差异。 小到微不足道,大到可以衡量。

循环了 1 亿次。

Baseline Time   : 00:00:00.5938502
Baseline +Method: 00:00:00.8098170
Marshal         : 00:00:10.5734336
Boxed Conversion: 00:00:01.9270779
GenericConv<T>  : 00:00:01.3276721
UnsafeConv<t>   : 00:00:02.4099777
(dynamic)Conv<T>: 00:00:02.8901075

令人惊讶的是,Marshal 提供了最慢的性能,而@MichaC 建议的解决方案提供了最佳性能。

这是基准代码:

    static void Main(string[] args)
    {
        int NumReps;
        NumReps = 100000000;
        Stopwatch SW;
        SW = new Stopwatch();
        byte[] buffer = new byte[4];

        for (int N = 0; N < 2; N++)
        {
            SW.Restart();
            for (int I = 0; I < NumReps; I++)
            {
                buffer = BitConverter.GetBytes(I);
            }
            SW.Stop();
        }
        Console.WriteLine(@"Baseline Time   : {0}", SW.Elapsed.ToString());

        for (int N = 0; N < 2; N++)
        {
            SW.Restart();
            for (int I = 0; I < NumReps; I++)
            {
                buffer = BitConversion(I);
            }
            SW.Stop();
        }
        Console.WriteLine(@"Baseline +Method: {0}", SW.Elapsed.ToString());

        for (int N = 0; N < 2; N++)
        {
            SW.Restart();
            for (int I = 0; I < NumReps; I++)
            {
                buffer = MarshalConv<int>(I);
            }
            SW.Stop();
        }
        Console.WriteLine(@"Marshal         : {0}", SW.Elapsed.ToString());

        for (int N = 0; N < 2; N++)
        {
            SW.Restart();
            for (int I = 0; I < NumReps; I++)
            {
                buffer = BoxedConversion<int>(I);
            }
            SW.Stop();
        }
        Console.WriteLine(@"Boxed Conversion: {0}", SW.Elapsed.ToString());

        GenericClass<int> GenericConverter = new GenericClass<int>();
        for (int N = 0; N < 2; N++)
        {
            SW.Restart();
            for (int I = 0; I < NumReps; I++)
            {
                buffer = GenericConverter.GetBytes(I);
            }
            SW.Stop();
        }
        Console.WriteLine(@"GenericConv<T>  : {0}", SW.Elapsed.ToString());

        for (int N = 0; N < 2; N++)
        {
            SW.Restart();
            for (int I = 0; I < NumReps; I++)
            {
                buffer = UnsafeConversion<int>(I);
            }
            SW.Stop();
        }
        Console.WriteLine(@"UnsafeConv<t>   : {0}", SW.Elapsed.ToString());

        for (int N = 0; N < 2; N++)
        {
            SW.Restart();
            for (int I = 0; I < NumReps; I++)
            {
                buffer = DynamicConv<int>(I);
            }
            SW.Stop();
        }
        Console.WriteLine(@"(dynamic)Conv<T>: {0}", SW.Elapsed.ToString());
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static byte[] BitConversion(int input)
    {
        return BitConverter.GetBytes(input);
    }

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

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

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

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

    public class GenericClass<T> where T : struct
    {
        IValueConverter<T> converter = new ValueConverter() as IValueConverter<T>;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte[] GetBytes(T value)
        {
            if (converter == null)
            {
                throw new InvalidOperationException("Unsuported type");
            }

            return converter.FromValue(value);
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static byte[] BoxedConversion<T>(T input)
        where T : struct, IConvertible
    {
        switch (Type.GetTypeCode(typeof(T)))
        {
            case TypeCode.Boolean:
                return BitConverter.GetBytes((bool)(object)input);
            case TypeCode.Byte:
                return BitConverter.GetBytes((byte)(object)input);
            case TypeCode.Char:
                return BitConverter.GetBytes((char)(object)input);
            case TypeCode.Double:
                return BitConverter.GetBytes((double)(object)input);
            case TypeCode.Int16:
                return BitConverter.GetBytes((short)(object)input);
            case TypeCode.Int32:
                return BitConverter.GetBytes((int)(object)input);
            case TypeCode.Int64:
                return BitConverter.GetBytes((long)(object)input);
            case TypeCode.SByte:
                return BitConverter.GetBytes((sbyte)(object)input);
            case TypeCode.Single:
                return BitConverter.GetBytes((float)(object)input);
            case TypeCode.UInt16:
                return BitConverter.GetBytes((ushort)(object)input);
            case TypeCode.UInt32:
                return BitConverter.GetBytes((uint)(object)input);
            case TypeCode.UInt64:
                return BitConverter.GetBytes((ulong)(object)input);
            default:
                throw new NotSupportedException(@"Unsupported privitive type. Example null reference or datetime.");
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static byte[] UnsafeConversion<T>(T input)
        where T : struct, IConvertible
    {
        switch (Type.GetTypeCode(typeof(T)))
        {
            case TypeCode.Boolean:
                return BitConverter.GetBytes(Unsafe.As < T, bool>(ref input));
            case TypeCode.Byte:
                return BitConverter.GetBytes(Unsafe.As < T, byte>(ref input));
            case TypeCode.Char:
                return BitConverter.GetBytes(Unsafe.As < T, char>(ref input));
            case TypeCode.Double:
                return BitConverter.GetBytes(Unsafe.As < T, double>(ref input));
            case TypeCode.Int16:
                return BitConverter.GetBytes(Unsafe.As < T, short>(ref input));
            case TypeCode.Int32:
                return BitConverter.GetBytes(Unsafe.As < T, int>(ref input));
            case TypeCode.Int64:
                return BitConverter.GetBytes(Unsafe.As < T, long>(ref input));
            case TypeCode.SByte:
                return BitConverter.GetBytes(Unsafe.As < T, sbyte>(ref input));
            case TypeCode.Single:
                return BitConverter.GetBytes(Unsafe.As < T, float>(ref input));
            case TypeCode.UInt16:
                return BitConverter.GetBytes(Unsafe.As < T, ushort>(ref input));
            case TypeCode.UInt32:
                return BitConverter.GetBytes(Unsafe.As < T, uint>(ref input));
            case TypeCode.UInt64:
                return BitConverter.GetBytes(Unsafe.As < T, ulong>(ref input));
            default:
                throw new NotSupportedException(@"Unsupported privitive type. Example null reference or datetime.");
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static byte[] DynamicConv<T>(T input)
        where T : struct, IConvertible
    {
        return BitConverter.GetBytes((dynamic)input);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte[] MarshalConv<T>(T input) where T : struct
    {
        int size = Marshal.SizeOf(typeof(T));
        var result = new byte[size];
        var gcHandle = GCHandle.Alloc(input, GCHandleType.Pinned);
        Marshal.Copy(gcHandle.AddrOfPinnedObject(), result, 0, size);
        gcHandle.Free();
        return result;
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM