web-dev-qa-db-fra.com

Génériques C # et vérification de type

J'ai une méthode qui utilise un IList<T> comme paramètre. Je dois vérifier quel est le type de cet objet T et faire quelque chose en fonction de celui-ci. J'essayais d'utiliser la valeur T, mais le compilateur ne l'autorise pas. Ma solution est la suivante:

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

Il doit y avoir une meilleure façon de procéder. Existe-t-il un moyen de vérifier le type de T transmis, puis d'utiliser une instruction switch?

64
Jon

Vous pouvez utiliser des surcharges:

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

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

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

Ou vous pouvez inspecter le type du paramètre générique:

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

Vous pouvez utiliser typeof(T).

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

Par défaut, sachez qu'il n'y a pas un excellent moyen. Il y a quelque temps, j'étais frustré par cela et j'ai écrit une petite classe d'utilité qui a un peu aidé et rendu la syntaxe un peu plus propre. Essentiellement, il transforme le code en

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

Un article de blog complet et des détails sur la mise en œuvre sont disponibles ici

6
JaredPar

Le type d'opérateur ...

typeof(T)

... ne fonctionnera pas avec l'instruction switch c #. Mais qu'en est-il? Le message suivant contient une classe statique ...

Existe-t-il une meilleure alternative que celle-ci pour 'activer le type'?

... qui vous permettra d'écrire du code comme ceci:

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"));
4
Robert Harvey

Pour tous ceux qui disent que vérifier les types et faire quelque chose en fonction du type n'est pas une bonne idée pour les génériques, je suis d'accord, mais je pense qu'il pourrait y avoir des circonstances où cela a parfaitement du sens.

Par exemple, si vous avez une classe qui dit est implémentée comme ceci (Remarque: je ne montre pas tout ce que ce code fait pour plus de simplicité et que vous avez simplement coupé et collé ici afin qu'il ne puisse pas construire ou fonctionner comme prévu comme le fait tout le code, mais il fait passer le message. L'unité est une énumération):

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

Donc, en résumé, je pense qu'il y a des raisons valables pour lesquelles vous voudrez peut-être vérifier de quel type est le générique, afin de faire quelque chose de spécial.

2
John

Il n'y a aucun moyen d'utiliser l'instruction switch pour ce que vous voulez qu'elle fasse. L'instruction switch doit être fournie avec des types intégraux, qui n'incluent pas les types complexes tels qu'un objet "Type" ou tout autre type d'objet d'ailleurs.

2
womp

Votre construction va complètement à l'encontre de l'objectif d'une méthode générique. C'est moche à dessein, car il doit y avoir une meilleure façon de réaliser ce que vous essayez d'accomplir, bien que vous ne nous ayez pas donné suffisamment d'informations pour comprendre ce que c'est.

2
mqp

Et, parce que C # a évolué, vous pouvez (maintenant) utiliser correspondance de modèle

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");
        }
    }
}
2
Kit

Vous pouvez faire typeOf(T), mais je revérifierais votre méthode et m'assurer que vous ne violez pas la responsabilité unique ici. Ce serait une odeur de code, et cela ne veut pas dire que cela ne devrait pas être fait, mais que vous devez être prudent.

Le but des génériques est de pouvoir construire des algorthims agnostiques de type si vous ne vous souciez pas du type ou tant qu'il correspond à un certain ensemble de critères. Votre implémentation n'est pas très générique.

1
JoshBerke

J'espère que ça t'as aidé:

  • typeof(IList<T>).IsGenericType == true
  • typeof(IList<T>).GetGenericTypeDefinition() == typeof(IList<>)
  • typeof(IList<int>).GetGenericArguments()[0] == typeof(int)

https://dotnetfiddle.net/5qUZnt

0
Jaider

Que dis-tu de ça :

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
0
Bert