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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15958830/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 18:36:50  来源:igfitidea点击:

C# generics: cast generic type to value type

c#genericscastingbitconverter

提问by rittergig

I have a generic class which saves value for the specified type T. The value can be an int, uint, double or 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. That is why I want to cast the value and call the specific overload of GetBytes(). My Question: How can I cast a generic value to int, double or float? This doesn't work:

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

Is there a possibility to cast an generic value? Or is there another way to get the bytes?

是否有可能投射通用值?还是有另一种方法来获取字节?

采纳答案by Eric Lippert

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 timesone for each kind of type, you haven't gained anything with generics.

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

回答by Jon Skeet

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 GetBytesbased on the type of T. Generics doesn't work well for that sort of thing. You coulduse dynamic typing to achieve it, in .NET 4 and above:

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

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

... but again this doesn't really feel like a nice design.

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

回答by Jim Mischel

You could potentially use Convert.ToInt32(this._value)or (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.

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

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

回答by H?kan Edling

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.

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

回答by jam40jeff

What would GenericClass<DateTime>do? 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:

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

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.

回答by MichaC

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

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

回答by Dejavu

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). 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++ (03/11/14) 背景以及对模板元编程的深刻理解,我在 C++11 中创建了一个类型泛型序列化库,代码重复最少(目标是拥有非重复代码,并且我相信我已经完成了 99%)。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. 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# 中实现一个更简单的序列化框架时,我正好卡在了 OP 发布的问题上。在 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! 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.

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