简体   繁体   English

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

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

I have a generic class which saves value for the specified type T. The value can be an int, uint, double or float.我有一个通用的 class,它保存指定类型 T 的值。该值可以是 int、uint、double 或 float。 Now I want to get the bytes of the value to encode it into an specific protocol.现在我想获取值的字节以将其编码为特定协议。 Therefore I want to use the method BitConverter.GetBytes() but unfortunately Bitconverter does not support generic types or undefined objects.因此我想使用方法 BitConverter.GetBytes() 但不幸的是 Bitconverter 不支持泛型类型或未定义的对象。 That is why I want to cast the value and call the specific overload of GetBytes().这就是为什么我要转换值并调用 GetBytes() 的特定重载。 My Question: How can I cast a generic value to int, double or float?我的问题:如何将通用值转换为 int、double 或 float? This doesn't work:这不起作用:

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

Is there a possibility to cast an generic value?有没有可能投一个通用值? Or is there another way to get the bytes?还是有另一种获取字节的方法?

First off, this is a really bad code smell.首先,这是一个非常糟糕的代码味道。 Any time you're doing a type test on a type parameter like this odds are good you're abusing generics.任何时候您对类型参数进行类型测试时,这种可能性都是很好的,您就是在滥用泛型。

The C# compiler knows that you are abusing generics in this way and disallows the cast from the value of type T to int, etc. You can turn off the compiler getting in your way by casting the value to object before you cast it to int: C# 编译器知道您正在以这种方式滥用泛型,并且不允许将类型 T 的值转换为 int 等。您可以通过在将值转换为 object 之前将其转换为 object 来关闭编译器:

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

Yuck.哎呀。 Again, it would be better to find another way to do this.同样,最好找到另一种方法来做到这一点。 For example:例如:

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

No generics necessary.不需要泛型。 Reserve generics for situations that are actually generic .储备仿制药是实际上通用的情况。 If you've written the code four times one for each kind of type, you haven't gained anything with generics.如果您为每种类型编写了四次代码,那么您对泛型没有任何好处。

Well, it strikes me that the type really isn't properly generic to start with: it can only be one of a few types, and you can't express that constraint.好吧,让我感到震惊的是,该类型一开始就真的不是通用的:它只能是少数类型之一,并且您无法表达该约束。

Then you want to call a different overload of GetBytes based on the type of T .然后您想根据T的类型调用不同的GetBytes重载。 Generics doesn't work well for that sort of thing.泛型在这种情况下效果不佳。 You could use dynamic typing to achieve it, in .NET 4 and above:可以在 .NET 4 及更高版本中使用动态类型来实现它:

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

... but again this doesn't really feel like a nice design. ……但这又不是一个很好的设计。

Late to the party, but just wanted to comment on the comments saying that the original proposal was a "bad design" - in my opinion, the original proposal (though it doesn't work) was not "necessarily" a bad design at all!晚会迟到,但只是想评论评论说原始提案是一个“糟糕的设计” - 在我看来,原始提案(虽然它不起作用)根本“不一定”是一个糟糕的设计!

Coming from a strong C++ (03/11/14) background with deep understanding of template meta-programming, I created a type generic serialization library in C++11 with minimal code repetition (the goal is to have non-repetitive code, and I believe I have achieved 99% of it).来自强大的 C++ (03/11/14) 背景以及对模板元编程的深刻理解,我在 C++11 中创建了一个类型泛型序列化库,代码重复最少(目标是拥有非重复代码,并且我相信我已经完成了 99%)。 The compile time template meta-programming facilities as provided by C++11, though can become extremely complex, helps achieve true type generic implementation of the serialization library. C++11 提供的编译时模板元编程工具虽然可能变得非常复杂,但有助于实现序列化库的真正类型泛型实现。

However, it is very unfortunate that when I wanted to implement a simpler serialization framework in C#, I was stuck exactly on the problem that the OP had posted.然而,非常不幸的是,当我想在 C# 中实现一个更简单的序列化框架时,我正好卡在了 OP 发布的问题上。 In C++, the template type T can be totally "forwarded" to the site of usage, while C# generics does not forward the actual compile time type to the usage site - any second (or more) level reference to a generic type T makes T becoming a distinct type that is not usable at all at the actual usage site, thus GetBytes(T) cannot determine that it should invoke a specific typed overload - worse, there is even no nice way in C# to say: hey, I know T is int, and if the compiler doesn't know it, does "(int)T" make it an int?在 C++ 中,模板类型 T 可以完全“转发”到使用站点,而 C# 泛型不会将实际编译时类型转发到使用站点 - 对泛型类型 T 的任何第二(或更多)级引用都会使 T成为一种在实际使用站点根本不可用的独特类型,因此 GetBytes(T) 无法确定它应该调用特定类型的重载 - 更糟糕的是,C# 中甚至没有好的方法可以说:嘿,我知道 T是int,如果编译器不知道,“(int)T”是否使它成为int?

Also, instead of blaming that type based switch has a smell of bad design - this has been a great misnomer that whenever people are doing some advanced type based framework and has to resort to type based switch due to inability of the language environment, without really understanding the constraints of the actual problem at hand, people starts to blatantly say type based switch is a bad design - it is, for most of the traditional OOP usage cases, but there are special cases, most of the time advanced usage case like the problem we are talking here, that this is necessary.此外,与其责怪基于类型的 switch 有一种糟糕的设计味道——这是一个很大的误称,当人们在做一些基于类型的高级框架并且由于语言环境的无能而不得不求助于基于类型的 switch 时,没有真正的了解手头实际问题的约束,人们开始公然说基于类型的 switch 是一个糟糕的设计 - 对于大多数传统的 OOP 用例来说是这样,但也有特殊情况,大多数时候高级用例像我们在这里谈论的问题,这是必要的。

It is also worth mentioning that I would actually blame that the BitConverter class is designed in a traditional and incompetent way to suit generic needs: instead of defining a type specific method for each type with regard to "GetBytes", maybe it would be more generic friendly to define a generic version of GetBytes(T value) - possibly with some constraints, so the user generic type T can be forwarded and work as expected without any type switch at all!还值得一提的是,我实际上会责怪 BitConverter 类以传统且无能的方式设计来满足通用需求:与其针对“GetBytes”为每种类型定义特定于类型的方法,不如说它更通用友好地定义 GetBytes(T value) 的通用版本 - 可能有一些约束,因此用户通用类型 T 可以被转发并按预期工作,根本不需要任何类型切换! The same is true for all the ToBool/ToXxx methods - if the .NET framework provides the facilities as non-generic version, how would one expect a generic framework trying to utilize this foundation framework - type switch or if without type switch, you end up duplicating the code logic for each data type you are trying to serialize - Oh, I miss the day I worked with C++ TMP that I only write the serialization logic once for practically unlimited number of types I can support.所有 ToBool/ToXxx 方法都是如此——如果 .NET 框架提供非通用版本的工具,人们如何期望通用框架尝试利用这个基础框架——类型切换或者如果没有类型切换,你结束为您尝试序列化的每种数据类型复制代码逻辑 - 哦,我怀念我使用 C++ TMP 的那一天,我只为我可以支持的几乎无限数量的类型编写了一次序列化逻辑。

Pretty late answer, but anyways... there is a way to make it slightly nicer... Make use of generics in a this way: Implement another generic type which converts the types for you.很晚的答案,但无论如何......有一种方法可以让它稍微好一点......以这种方式使用泛型:实现另一种为你转换类型的泛型类型。 So you don't have to care about unboxing, casting etc of the type to object... it will just work.因此,您不必关心取消装箱,将类型转换为对象等......它会起作用。

Also, in your GenericClass, now you don't have to switch the types, you can just use IValueConverter<T> and also cast it as IValueConverter<T> .此外,在您的 GenericClass 中,现在您不必切换类型,您只需使用IValueConverter<T>并将其转换as IValueConverter<T> This way, generics will do the magic for you to find the correct interface implementation, and in addition, the object will be null if T is something you do not support...这样,泛型将为您找到正确的接口实现魔术,此外,如果 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);
    }
}

You could potentially use Convert.ToInt32(this._value) or (int)((object)this._value) .您可能会使用Convert.ToInt32(this._value)(int)((object)this._value) But in general if you find yourself having to check for specific types in a generic method, there's a problem with your design.但一般来说,如果您发现自己必须检查泛型方法中的特定类型,那么您的设计就有问题。

In your case, you probably should consider making an abstract base class, and then derived classes for the types you're going to use:在您的情况下,您可能应该考虑创建一个抽象基类,然后为要使用的类型创建派生类:

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

If your only goal is to add the GetBytes method to these types, isn't it a much nicer solution to add them as extension methods like so:如果您的唯一目标是将 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) ;
    }
}

If you really need you generic class for other purposes, just do the dirty "double typecast" like Eric mentioned where you typecast value to object first.如果你真的需要你的泛型类用于其他目的,只需像埃里克提到的那样首先将值类型转换为对象,就可以进行肮脏的“双重类型转换”。

What would GenericClass<DateTime> do? GenericClass<DateTime>做什么? Rather, it seems you have a discrete set of classes which know how to get their bytes, so make an abstract base class that does all of the common work, and then make 3 concrete class which override a method to specify the piece that changes between them:相反,您似乎有一组离散的类,它们知道如何获取它们的字节,因此创建一个抽象基类来完成所有常见的工作,然后创建 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);
    }
}

This not only provides clean, strongly-typed implementations of your three known types, but leaves the door open for anyone else to subclass Generic<T> and provide an appropriate implementation of GetBytes .这不仅为您的三种已知类型提供了干净的强类型实现,而且为其他任何人打开了子类Generic<T>并提供了GetBytes的适当实现的GetBytes

I am on the side that this is an interesting real life problem that has nothing to do with "bad" design but rather standard C# limitations.我认为这是一个有趣的现实生活问题,与“糟糕”的设计无关,而是与标准的 C# 限制有关。 Casting through object通过object投射

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

or, in the current language as或者,在当前语言中

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

works but causes a performance hit due to ValueType boxing.有效,但由于 ValueType 装箱而导致性能下降。

The solution to this problem is Unsafe.As<TFrom,TTo>() from System.Runtime.CompilerServices.Unsafe NuGet package:这个问题的解决方案是Unsafe.As<TFrom,TTo>()来自System.Runtime.CompilerServices.Unsafe NuGet 包:

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

Results in no explicit or implicit cast to object .结果不会显式或隐式转换为object

Performance test of the methods suggested here along with others found elsewhere:此处建议的方法以及其他地方发现的其他方法的性能测试:

Note: [MethodImpl(MethodImplOptions.AggressiveInlining)] Provides a very small but measurable performance difference.注意:[MethodImpl(MethodImplOptions.AggressiveInlining)] 提供非常小但可测量的性能差异。 Small enough to be insignificant but big enough to be measurable.小到微不足道,大到可以衡量。

Looped 100 million times.循环了 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

Surprisingly Marshal provides the slowest performance While the solution suggested by @MichaC provides the best performance.令人惊讶的是,Marshal 提供了最慢的性能,而@MichaC 建议的解决方案提供了最佳性能。

Here is the benchmark code:这是基准代码:

    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