web-dev-qa-db-fra.com

Vérification du type: typeof, GetType, ou is?

J'ai vu beaucoup de gens utiliser le code suivant:

Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

Mais je sais que vous pourriez aussi faire ceci:

if (obj1.GetType() == typeof(int))
    // Some code here

Ou ca:

if (obj1 is int)
    // Some code here

Personnellement, je pense que le dernier est le plus propre, mais est-ce qu'il me manque quelque chose? Lequel est le meilleur à utiliser, ou est-ce une préférence personnelle?

1406
jasonh

Tous sont différents.

  • typeof prend un nom de type (que vous spécifiez lors de la compilation).
  • GetType récupère le type d'exécution d'une instance.
  • is renvoie true si une instance est dans l'arbre d'héritage.

Exemple

class Animal { } 
class Dog : Animal { }

void PrintTypes(Animal a) { 
    Console.WriteLine(a.GetType() == typeof(Animal)); // false 
    Console.WriteLine(a is Animal);                   // true 
    Console.WriteLine(a.GetType() == typeof(Dog));    // true
    Console.WriteLine(a is Dog);                      // true 
}

Dog spot = new Dog(); 
PrintTypes(spot);

Qu'en est-il de typeof(T)? Est-ce également résolu au moment de la compilation?

Oui. T est toujours ce que le type de l'expression est. Rappelez-vous, une méthode générique est fondamentalement un tas de méthodes avec le type approprié. Exemple:

string Foo<T>(T parameter) { return typeof(T).Name; }

Animal probably_a_dog = new Dog();
Dog    definitely_a_dog = new Dog();

Foo(probably_a_dog); // this calls Foo<Animal> and returns "Animal"
Foo<Animal>(probably_a_dog); // this is exactly the same as above
Foo<Dog>(probably_a_dog); // !!! This will not compile. The parameter expects a Dog, you cannot pass in an Animal.

Foo(definitely_a_dog); // this calls Foo<Dog> and returns "Dog"
Foo<Dog>(definitely_a_dog); // this is exactly the same as above.
Foo<Animal>(definitely_a_dog); // this calls Foo<Animal> and returns "Animal". 
Foo((Animal)definitely_a_dog); // this does the same as above, returns "Animal"
1723
Jimmy

Utilisez typeof lorsque vous souhaitez obtenir le type à la compilation . Utilisez GetType lorsque vous souhaitez obtenir le type à heure d'exécution . Il y a rarement des cas où is est utilisé comme une distribution et, dans la plupart des cas, vous finissez par convertir la variable.

Il existe une quatrième option que vous n'avez pas envisagée (en particulier si vous souhaitez également convertir un objet dans le type trouvé); c'est-à-dire utiliser as.

Foo foo = obj as Foo;

if (foo != null)
    // your code here

Ceci utilise seulement n cast alors que cette approche:

if (obj is Foo)
    Foo foo = (Foo)obj;

nécessite deux.

180
Andrew Hare

1.

Type t = typeof(obj1);
if (t == typeof(int))

Ceci est illégal, car typeof ne fonctionne que sur les types, pas sur les variables. Je suppose que obj1 est une variable. Donc, de cette manière, typeof est statique et fait son travail lors de la compilation au lieu de l'exécution.

2.

if (obj1.GetType() == typeof(int))

Ceci est vrai si obj1 est exactement du type int. Si obj1 dérive de int, la condition if sera fausse.

.

if (obj1 is int)

Cela est vrai si obj1 est un int, ou s'il dérive d'une classe appelée int, ou s'il implémente une interface appelée int.

68
Scott Langham
Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

C'est une erreur. L'opérateur typeof en C # ne peut prendre que des noms de types, pas d'objets.

if (obj1.GetType() == typeof(int))
    // Some code here

Cela fonctionnera, mais peut-être pas comme prévu. Comme vous l'avez montré ici, cela est acceptable pour les types de valeur, mais pour les types de référence, il ne renverrait true que si le type était du type exactement identique, et non quelque chose d'autre dans la hiérarchie d'héritage. Par exemple:

class Animal{}
class Dog : Animal{}

static void Foo(){
    object o = new Dog();

    if(o.GetType() == typeof(Animal))
        Console.WriteLine("o is an animal");
    Console.WriteLine("o is something else");
}

Ceci afficherait "o is something else", car le type de o est Dog et non pas Animal. Vous pouvez toutefois utiliser cette méthode si vous utilisez la méthode IsAssignableFrom de la classe Type.

if(typeof(Animal).IsAssignableFrom(o.GetType())) // note use of tested type
    Console.WriteLine("o is an animal");

Cette technique reste cependant un problème majeur. Si votre variable est null, l'appel à GetType() lève une exception NullReferenceException. Donc, pour que cela fonctionne correctement, vous feriez:

if(o != null && typeof(Animal).IsAssignableFrom(o.GetType()))
    Console.WriteLine("o is an animal");

Avec cela, vous avez un comportement équivalent du mot clé is. Par conséquent, si tel est le comportement souhaité, vous devez utiliser le mot clé is, qui est plus lisible et plus efficace.

if(o is Animal)
    Console.WriteLine("o is an animal");

Dans la plupart des cas, cependant, le mot clé is n'est toujours pas ce que vous voulez vraiment, car il ne suffit généralement pas de savoir qu'un objet appartient à un certain type. Habituellement, vous voulez réellement tiliser cet objet en tant qu'instance de ce type, ce qui nécessite également de le lancer. Et vous pouvez donc vous retrouver à écrire un code comme celui-ci:

if(o is Animal)
    ((Animal)o).Speak();

Mais cela fait que le CLR vérifie le type de l'objet jusqu'à deux fois. Il le vérifiera une fois pour satisfaire l'opérateur is, et si o est bien un Animal, nous le ferons vérifier à nouveau pour valider la conversion.

C'est plus efficace de faire cela à la place:

Animal a = o as Animal;
if(a != null)
    a.Speak();

L'opérateur as est une distribution qui ne lève pas d'exception si elle échoue, mais renvoie plutôt null. De cette façon, le CLR vérifie le type de l'objet une seule fois, après quoi nous devons simplement effectuer un contrôle nul, ce qui est plus efficace.

Mais méfiez-vous: beaucoup de gens tombent dans un piège avec as. Parce qu’il ne jette pas d’exceptions, certains le voient comme un casting "sûr", et ils l’utilisent exclusivement, évitant les castes ordinaires. Cela conduit à des erreurs comme celle-ci:

(o as Animal).Speak();

Dans ce cas, le développeur suppose clairement que o sera toujours sera un Animal, et tant que leur hypothèse est correcte, tout se passe bien. Mais s'ils se trompent, alors ils se retrouvent avec un NullReferenceException. Avec une distribution régulière, ils auraient obtenu un InvalidCastException, ce qui aurait permis de mieux identifier le problème.

Parfois, ce bug peut être difficile à trouver:

class Foo{
    readonly Animal animal;

    public Foo(object o){
        animal = o as Animal;
    }

    public void Interact(){
        animal.Speak();
    }
}

Ceci est un autre cas où le développeur s'attend clairement à ce que o soit un Animal à chaque fois, mais cela n'est pas évident dans le constructeur, où la conversion as est utilisée. Ce n'est pas évident jusqu'à ce que vous obteniez la méthode Interact, où le champ animal devrait être affecté positivement. Dans ce cas, non seulement vous vous retrouvez avec une exception trompeuse, mais elle n'est levée que potentiellement plus tard que lorsque l'erreur s'est produite.

En résumé:

  • Si vous avez seulement besoin de savoir si un objet est ou non d'un type, utilisez is.

  • Si vous devez traiter un objet comme une instance d'un certain type, mais que vous ne savez pas si l'objet sera de ce type, utilisez as et recherchez null.

  • Si vous devez traiter un objet comme une instance d'un certain type et que l'objet est supposé appartenir à ce type, utilisez une distribution normale.

48
P Daddy

J'avais une propriété Type- à comparer et je ne pouvais pas utiliser is (comme my_type is _BaseTypetoLookFor), mais je pouvais les utiliser:

base_type.IsInstanceOfType(derived_object);
base_type.IsAssignableFrom(derived_type);
derived_type.IsSubClassOf(base_type);

Notez que IsInstanceOfType et IsAssignableFrom renvoient true lorsqu’on compare les mêmes types, où IsSubClassOf renverra false. Et IsSubclassOf ne fonctionne pas sur les interfaces, contrairement aux deux autres. (Voir aussi cette question et cette réponse .)

public class Animal {}
public interface ITrainable {}
public class Dog : Animal, ITrainable{}

Animal dog = new Dog();

typeof(Animal).IsInstanceOfType(dog);     // true
typeof(Dog).IsInstanceOfType(dog);        // true
typeof(ITrainable).IsInstanceOfType(dog); // true

typeof(Animal).IsAssignableFrom(dog.GetType());      // true
typeof(Dog).IsAssignableFrom(dog.GetType());         // true
typeof(ITrainable).IsAssignableFrom(dog.GetType()); // true

dog.GetType().IsSubclassOf(typeof(Animal));            // true
dog.GetType().IsSubclassOf(typeof(Dog));               // false
dog.GetType().IsSubclassOf(typeof(ITrainable)); // false
13
Yahoo Serious

Si vous utilisez C # 7, il est temps de mettre à jour la superbe réponse d'Andrew Hare. Pattern matching a introduit un raccourci Nice qui nous donne une variable typée dans le contexte de l'instruction if, sans nécessiter de déclaration/conversion et vérification distinctes:

if (obj1 is int integerValue)
{
    integerValue++;
}

Cela semble assez décevant pour un seul casting comme celui-ci, mais brille vraiment lorsque de nombreux types sont possibles dans votre routine. La méthode ci-dessous est l’ancienne façon d’éviter de lancer deux fois:

Button button = obj1 as Button;
if (button != null)
{
    // do stuff...
    return;
}
TextBox text = obj1 as TextBox;
if (text != null)
{
    // do stuff...
    return;
}
Label label = obj1 as Label;
if (label != null)
{
    // do stuff...
    return;
}
// ... and so on

Travailler à réduire autant que possible ce code et éviter les doublons du même objet m’a toujours dérangé. Ce qui précède est bien compressé avec un motif correspondant à ce qui suit:

switch (obj1)
{
    case Button button:
        // do stuff...
        break;
    case TextBox text:
        // do stuff...
        break;
    case Label label:
        // do stuff...
        break;
    // and so on...
}

EDIT: Mise à jour de la nouvelle méthode plus longue pour utiliser un commutateur conformément au commentaire de Palec.

12
JoelC

Je préfère est

Cela dit, si vous utilisez est, vous êtes probablement pas utiliser l'héritage correctement.

Supposons que cette personne: entité et cet animal: entité. Feed est une méthode virtuelle dans Entity (pour rendre Neil heureux)

class Person
{
  // A Person should be able to Feed
  // another Entity, but they way he feeds
  // each is different
  public override void Feed( Entity e )
  {
    if( e is Person )
    {
      // feed me
    }
    else if( e is Animal )
    {
      // ruff
    }
  }
}

Plutôt

class Person
{
  public override void Feed( Person p )
  {
    // feed the person
  }
  public override void Feed( Animal a )
  {
    // feed the animal
  }
}
9
bobobobo

Je crois que le dernier concerne également l'héritage (par exemple, Dog is Animal == true), ce qui est préférable dans la plupart des cas.

5
StriplingWarrior

Cela dépend de ce que je fais. Si j'ai besoin d'une valeur bool (par exemple, pour déterminer si je vais utiliser un int), je vais utiliser is. Si j'ai réellement besoin du type pour une raison quelconque (par exemple, pour passer à une autre méthode), je vais utiliser GetType().

2
AllenG

Utilisé pour obtenir l'objet System.Type pour un type. Une expression typeof prend la forme suivante:

System.Type type = typeof(int);

Example:

    public class ExampleClass
    {
       public int sampleMember;
       public void SampleMethod() {}

       static void Main()
       {
          Type t = typeof(ExampleClass);
          // Alternatively, you could use
          // ExampleClass obj = new ExampleClass();
          // Type t = obj.GetType();

          Console.WriteLine("Methods:");
          System.Reflection.MethodInfo[] methodInfo = t.GetMethods();

          foreach (System.Reflection.MethodInfo mInfo in methodInfo)
             Console.WriteLine(mInfo.ToString());

          Console.WriteLine("Members:");
          System.Reflection.MemberInfo[] memberInfo = t.GetMembers();

          foreach (System.Reflection.MemberInfo mInfo in memberInfo)
             Console.WriteLine(mInfo.ToString());
       }
    }
    /*
     Output:
        Methods:
        Void SampleMethod()
        System.String ToString()
        Boolean Equals(System.Object)
        Int32 GetHashCode()
        System.Type GetType()
        Members:
        Void SampleMethod()
        System.String ToString()
        Boolean Equals(System.Object)
        Int32 GetHashCode()
        System.Type GetType()
        Void .ctor()
        Int32 sampleMember
    */

Cet exemple utilise la méthode GetType pour déterminer le type utilisé pour contenir le résultat d'un calcul numérique. Cela dépend des besoins de stockage du nombre résultant.

    class GetTypeTest
    {
        static void Main()
        {
            int radius = 3;
            Console.WriteLine("Area = {0}", radius * radius * Math.PI);
            Console.WriteLine("The type is {0}",
                              (radius * radius * Math.PI).GetType()
            );
        }
    }
    /*
    Output:
    Area = 28.2743338823081
    The type is System.Double
    */
0
Muhammad Awais

Le dernier est plus propre, plus évident et vérifie également les sous-types. Les autres ne vérifient pas le polymorphisme.

0
thecoop