C# generics: cast generic type to value type

2020-07-02 11:04发布

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:

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?

7条回答
一夜七次
2楼-- · 2020-07-02 11:16

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.

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);
    }
}
查看更多
▲ chillily
3楼-- · 2020-07-02 11:21

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:

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.

查看更多
乱世女痞
4楼-- · 2020-07-02 11:21

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.

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?

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.

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.

查看更多
做自己的国王
5楼-- · 2020-07-02 11:24

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

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);
    }
}
查看更多
我欲成王,谁敢阻挡
6楼-- · 2020-07-02 11:25

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:

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.

查看更多
混吃等死
7楼-- · 2020-07-02 11:37

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

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

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

查看更多
登录 后发表回答