C# generics: cast generic type to value type

前端 未结 8 2575
佛祖请我去吃肉
佛祖请我去吃肉 2021-02-19 03:51

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

相关标签:
8条回答
  • 2021-02-19 04:17

    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.

    0 讨论(0)
  • 2021-02-19 04:18

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

    The solution to this problem is Unsafe.As<TFrom,TTo>() from System.Runtime.CompilerServices.Unsafe NuGet package:

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

    Results in no explicit or implicit cast to object.

    0 讨论(0)
  • 2021-02-19 04: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);
        }
    }
    
    0 讨论(0)
  • 2021-02-19 04:27

    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.

    0 讨论(0)
  • 2021-02-19 04:28

    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);
        }
    }
    
    0 讨论(0)
  • 2021-02-19 04:32

    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.

    0 讨论(0)
提交回复
热议问题