C#ジェネリックスとタイプチェック


83

IList<T>パラメータとしてを使用するメソッドがあります。そのTオブジェクトのタイプを確認し、それに基づいて何かを行う必要があります。T値を使おうとしましたが、コンパイラが許可していません。私の解決策は次のとおりです。

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        if (clause[0] is int || clause[0] is decimal)
        {
           //do something
        }
        else if (clause[0] is String)
        {
           //do something else
        }
        else if (...) //etc for all the types
        else
        {
           throw new ApplicationException("Invalid type");
        }
    } 
}

これを行うには、より良い方法が必要です。T渡されたタイプを確認してからswitchステートメントを使用する方法はありますか?


1
個人的には、データ型ごとに特別に何をしているのか知りたいです。データ型ごとにほぼ同じ変換を行う場合は、異なる型を共通のインターフェイスにマップして、そのインターフェイスで操作する方が簡単な場合があります。
ジュリエット

回答:


121

オーバーロードを使用できます:

public static string BuildClause(List<string> l){...}

public static string BuildClause(List<int> l){...}

public static string BuildClause<T>(List<T> l){...}

または、ジェネリックパラメーターのタイプを調べることができます。

Type listType = typeof(T);
if(listType == typeof(int)){...}

23
+1:過負荷は、設計と長期的な保守性の観点から、ここで間違いなく最良の解決策です。ジェネリックパラメーターの実行時型チェックは、真っ直ぐな顔でコーディングするには皮肉すぎるようです。
ジュリエット

これが役立つ場合の良い例は、大きく変化するタイプの一般的なシリアル化です。渡されるオブジェクトが文字列である場合、なぜ余分な作業を行うのですか?文字列の場合は、追加の処理を行わずに元の文字列を返すだけです
watkinsmatthewp 2015年

申し訳ありませんが、switch-case代わりにを使用してそれを達成する方法はありif-elseますか?
TAN

残念ながら@HappyCoding =(あなたは、C#の次のバージョンでそうすることができるかもしれません。
jonnii

7
より特殊なオーバーロードが呼び出されることを保証できないため、オーバーロードが機能的に異なる場合(副作用も考慮してください)、一般的なオーバーロード(この回答を参照)に依存しないでください。ここでのルールは次のとおりです。特定のタイプに特化したロジックを実行する必要がある場合は、そのタイプをチェックし、オーバーロードを使用しないようにする必要があります。ただし、特殊なロジック(つまり、パフォーマンスの向上)を実行することだけを優先し、一般的なケースを含むすべてのオーバーロードで同じ結果が得られる場合は、型チェックの代わりにオーバーロードを使用できます。
tomosius 2017年

23

を使用できますtypeof(T)

private static string BuildClause<T>(IList<T> clause)
{
     Type itemType = typeof(T);
     if(itemType == typeof(int) || itemType == typeof(decimal))
    ...
}

7

デフォルトでは、優れた方法はありません。しばらく前、私はこれに不満を感じ、少し助けて構文を少しきれいにする小さなユーティリティクラスを書きました。本質的にそれはコードをに変えます

TypeSwitcher.Do(clause[0],
  TypeSwitch.Case<int>(x => ...),  // x is an int
  TypeSwitch.Case<decimal>(d => ...), // d is a decimal 
  TypeSwitch.Case<string>(s => ...)); // s is a string

完全なブログ投稿と実装の詳細は、こちらから入手できます。


6

また、C#が進化したため、(現在)パターンマッチングを使用できます。

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        switch (clause[0])
        {
            case int x: // do something with x, which is an int here...
            case decimal x: // do something with x, which is a decimal here...
            case string x: // do something with x, which is a string here...
            ...
            default: throw new ApplicationException("Invalid type");
        }
    }
}

また、C#8.0のスイッチ式を使用すると、構文がさらに簡潔になります。

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        return clause[0] switch
        {
            int x => "some string related to this int",
            decimal x => "some string related to this decimal",
            string x => x,
            ...,
            _ => throw new ApplicationException("Invalid type")
        }
    }
}

4

typeof演算子...

typeof(T)

... c#switchステートメントでは機能しません。しかし、これはどうですか?次の投稿には静的クラスが含まれています...

'スイッチオンタイプ'にこれよりも良い代替手段はありますか?

...これにより、次のようなコードを記述できます。

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));

こちらのJaredParの回答もご覧ください。
ロバートハーベイ

3

型をチェックし、型に基づいて何かをすることはジェネリックにとっては良い考えではないと言うすべての人にとって、私はある程度同意しますが、これが完全に理にかなっている状況があるかもしれないと思います。

たとえば、そのように実装されていると言うクラスがある場合(注:簡単にするためにこのコードが行うことをすべて示しているわけではなく、ここに切り取って貼り付けただけなので、コード全体のようにビルドまたは動作しない可能性がありますが、また、ユニットは列挙型です):

public class FoodCount<TValue> : BaseFoodCount
{
    public TValue Value { get; set; }

    public override string ToString()
    {
        if (Value is decimal)
        {
            // Code not cleaned up yet
            // Some code and values defined in base class

            mstrValue = Value.ToString();
            decimal mdecValue;
            decimal.TryParse(mstrValue, out mdecValue);

            mstrValue = decimal.Round(mdecValue).ToString();

            mstrValue = mstrValue + mstrUnitOfMeasurement;
            return mstrValue;
        }
        else
        {
            // Simply return a string
            string str = Value.ToString() + mstrUnitOfMeasurement;
            return str;
        }
    }
}

..。

public class SaturatedFat : FoodCountWithDailyValue<decimal>
{
    public SaturatedFat()
    {
        mUnit = Unit.g;
    }

}

public class Fiber : FoodCount<int>
{
    public Fiber()
    {
        mUnit = Unit.g;
    }
}

public void DoSomething()
{
       nutritionFields.SaturatedFat oSatFat = new nutritionFields.SaturatedFat();

       string mstrValueToDisplayPreFormatted= oSatFat.ToString();
}

要約すると、何か特別なことをするために、ジェネリックがどのタイプであるかを確認したいという正当な理由があると思います。


2

実行したい目的でswitchステートメントを使用する方法はありません。switchステートメントには、「Type」オブジェクトなどの複合型やその他のオブジェクト型を含まない整数型を指定する必要があります。


2

あなたの構築は、一般的な方法の目的を完全に打ち破ります。あなたがそれが何であるかを理解するのに十分な情報を私たちに与えていないけれども、あなたが達成しようとしていることを達成するためのより良い方法がなければならないので、それは意図的に醜いです。


2

できるよ typeOf(T)が、私はあなたの方法を再確認し、ここであなたが単一の責任に違反していないことを確認します。これはコードの臭いであり、それを行うべきではないということではありませんが、注意する必要があります。

ジェネリックスのポイントは、タイプが何であるかを気にしない場合、または特定の基準セット内に収まる限り、タイプにとらわれないアルゴリズムを構築できることです。あなたの実装はあまり一般的ではありません。



0

これはどう :

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.