web-dev-qa-db-ja.com

C#ジェネリック:ジェネリック型を値型にキャスト

指定された型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);
        }
    }
}

ジェネリック値をキャストする可能性はありますか?または、バイトを取得する別の方法はありますか?

13
rittergig

まず第一に、これは本当に悪いコードの臭いです。このオッズのような型パラメーターで型テストを行うときはいつでも、ジェネリックを悪用しているのは良いことです。

C#コンパイラは、この方法でジェネリックを悪用していることを認識し、T型の値からintなどへのキャストを禁止します。intにキャストする前にオブジェクトに値をキャストすることで、コンパイラが邪魔にならないようにすることができます。

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

ジェネリックは必要ありません。実際にgenericである状況のためにジェネリックを予約します。コードを書いた場合4回タイプの種類ごとに1つ、ジェネリックスでは何も得られません。

29
Eric Lippert

そもそも、この型は実際には適切に一般的ではないことに気づきました。それは数少ない型の1つにすぎず、その制約を表現することはできません。

次に、GetBytesのタイプに基づいて、Tの異なるオーバーロードを呼び出します。ジェネリックスはそのようなことにはうまく機能しません。あなたcould .NET 4以降では、動的型付けを使用してそれを実現します。

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

...しかし、これも本当に素敵なデザインのようには感じません。

13
Jon Skeet

かなり遅い答えですが、とにかく...少し良くする方法があります...このようにジェネリックを利用します:あなたのために型を変換する別のジェネリック型を実装します。したがって、オブジェクトへのタイプの開封、キャストなどを気にする必要はありません...それはうまくいきます。

また、GenericClassでは、タイプを切り替える必要はありません。IValueConverter<T>を使用して、as IValueConverter<T>をキャストすることもできます。このように、ジェネリックスは正しいインターフェース実装を見つけるための魔法を実行します。さらに、Tがサポートされていないものである場合、オブジェクトはnullになります...

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

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

他の目的でジェネリッククラスが本当に必要な場合は、最初にオブジェクトに値をタイプキャストするエリックが述べたような汚い「ダブルタイプキャスト」を実行してください。

3
Håkan Edling

パーティーに遅れましたが、元の提案は「悪いデザイン」だったというコメントにコメントしたかっただけです。私の意見では、元の提案は(機能しませんが)「必ずしも」悪いデザインではありませんでした。 !!

テンプレートメタプログラミングを深く理解している強力なC++(03/11/14)のバックグラウンドから来て、最小限のコード反復でC++ 11で型ジェネリックシリアル化ライブラリを作成しました(目標は非反復コードを持つことであり、私はそれの99%を達成したと信じています)。 C++ 11によって提供されるコンパイル時テンプレートメタプログラミング機能は、非常に複雑になる可能性がありますが、シリアル化ライブラリのTrueTypeジェネリック実装を実現するのに役立ちます。

ただし、C#でより単純なシリアル化フレームワークを実装したいときに、OPが投稿した問題に正確に固執したことは非常に残念です。 C++では、テンプレートタイプTを使用サイトに完全に「転送」できますが、C#ジェネリックは実際のコンパイル時タイプを使用サイトに転送しません-ジェネリックタイプTへの2番目(またはそれ以上)のレベルの参照はTを作成します実際の使用サイトではまったく使用できない別個の型になるため、GetBytes(T)は、特定の型付きオーバーロードを呼び出す必要があるかどうかを判断できません-さらに悪いことに、C#には次のような良い方法さえありません:ねえ、私はTを知っていますはintであり、コンパイラがそれを知らない場合、「(int)T」はそれをintにしますか?

また、タイプベースのスイッチが悪いデザインの匂いを持っていると非難する代わりに-これは、人々が高度なタイプベースのフレームワークを実行しているときはいつでも、言語環境の能力がないためにタイプベースのスイッチに頼らなければならないという大きな誤解です。手元にある実際の問題の制約を理解すると、人々はタイプベースのスイッチは悪い設計だと露骨に言い始めます-それは、ほとんどの従来のOOP使用例ですが、特別な場合があります、ほとんどの場合、ここで話している問題のような高度なユースケースでは、これが必要です。

また、BitConverterクラスが一般的なニーズに合わせて従来の無能な方法で設計されていることを実際に非難することにも言及する価値があります。「GetBytes」に関して各型に型固有のメソッドを定義する代わりに、より一般的な方法になる可能性があります。 GetBytes(T value)のジェネリックバージョンを定義するのは簡単です-おそらくいくつかの制約があるので、ユーザージェネリックタイプTを転送して、タイプスイッチをまったく使わなくても期待どおりに機能できます!同じことがすべてのToBool/ToXxxメソッドにも当てはまります-.NETフレームワークが非汎用バージョンとして機能を提供する場合、この基盤フレームワークを利用しようとする汎用フレームワークをどのように期待しますか-タイプスイッチまたはタイプスイッチなしの場合は終了しますシリアル化しようとしているデータ型ごとにコードロジックを複製する-ああ、C++ TMPで作業した日が恋しいです。サポートできるタイプの数が実質的に無制限の場合、シリアル化ロジックを1回だけ記述します。

3
Dejavu

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);
    }
}
3
Jim Mischel

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

これにより、既知の3つのタイプのクリーンで強い型の実装が提供されるだけでなく、他の誰もがGeneric<T>をサブクラス化し、GetBytesの適切な実装を提供できるようになります。

2
jam40jeff