Question Type de vérification: typeof, GetType ou est?


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 tu pourrais 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 y a-t-il quelque chose qui me manque? Lequel est le meilleur à utiliser, ou est-ce une préférence personnelle?


1187
2018-06-11 19:10


origine


Réponses:


Tous sont différents.

  • typeof prend un nom de type (que vous spécifiez à la compilation).
  • GetType obtient le type d'exécution d'une instance.
  • is renvoie true si une instance se trouve 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-il é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 essentiellement tout un tas de méthodes avec le type approprié. Exemple:

string Foo<T>(T object) { 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"

1496
2018-06-11 19:15



Utilisation typeof quand vous voulez obtenir le type à temps de compilation. Utilisation GetType quand vous voulez obtenir le type à temps d'exécution. Il y a rarement des cas à utiliser is comme dans le cas d'une distribution et, dans la plupart des cas, vous finissez par lancer la variable de toute façon.

Il y a une quatrième option que vous n'avez pas envisagée (surtout si vous allez lancer un objet sur le type que vous trouvez aussi); c'est utiliser as.

Foo foo = obj as Foo;

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

Ceci seulement utilise un jeter alors que cette approche:

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

a besoin deux.


163
2018-06-11 19:14



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 façon, typeof est statique, et fait son travail au moment de la compilation au lieu de l'exécution.

2.

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

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

3.

if (obj1 is int)

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


59
2018-06-11 19:17



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

Ceci est une erreur L'opérateur typeof en C # peut uniquement prendre des noms de type, pas des objets.

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

Cela fonctionnera, mais peut-être pas comme prévu. Pour les types de valeur, comme vous l'avez montré ici, c'est acceptable, mais pour les types de référence, cela ne retournerait vrai que si le type était le exactement la même type, pas autre chose 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");
}

Cela imprimerait "o is something else", parce que le type de o est Dog, ne pas Animal. Vous pouvez faire ce travail, cependant, si vous utilisez le IsAssignableFrom méthode de Type classe.

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

Cependant, cette technique laisse toujours un problème majeur. Si votre variable est nulle, l'appel à GetType() lancera une exception NullReferenceException. Donc, pour le faire fonctionner correctement, vous feriez:

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

Avec cela, vous avez un comportement équivalent de la is mot-clé. Par conséquent, si c'est le comportement que vous voulez, vous devez utiliser le is mot-clé, qui est plus lisible et plus efficace.

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

Dans la plupart des cas, cependant, le is Le mot clé n'est toujours pas ce que vous voulez vraiment, car il n'est généralement pas suffisant de savoir qu'un objet est d'un certain type. Habituellement, vous voulez réellement utilisation cet objet en tant qu'instance de ce type, ce qui nécessite de le lancer aussi. Et ainsi vous pourriez vous retrouver à écrire du code comme ceci:

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

Mais cela fait que le CLR vérifie le type de l'objet jusqu'à deux fois. Il va vérifier une fois pour satisfaire le is opérateur, et si o est en effet un Animal, nous le faisons vérifier à nouveau pour valider le casting.

Il est plus efficace de le faire à la place:

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

le as L'opérateur est une distribution qui ne lancera pas une exception si elle échoue, mais retournera null. De cette façon, le CLR vérifie le type de l'objet juste une fois, et après cela, nous avons juste besoin de faire une vérification nulle, ce qui est plus efficace.

Mais attention: beaucoup de gens tombent dans un piège avec as. Parce qu'il ne lance pas d'exceptions, certaines personnes pensent qu'il s'agit d'un casting "sûr", et ils l'utilisent exclusivement, évitant les lancers réguliers. Cela conduit à des erreurs comme celle-ci:

(o as Animal).Speak();

Dans ce cas, le développeur suppose clairement que o volonté toujours haricot Animal, et tant que leur hypothèse est correcte, tout fonctionne bien. Mais si elles ont tort, alors ce qu'ils se retrouvent ici est un NullReferenceException. Avec une distribution régulière, ils auraient eu un InvalidCastException au lieu de cela, qui aurait plus correctement identifié 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 attend clairement o être un Animal à chaque fois, mais ce n'est pas évident dans le constructeur, où le as le cast est utilisé. Ce n'est pas évident jusqu'à ce que vous arrivez à la Interact méthode, où le animal Ce champ devrait être attribué de manière positive. Dans ce cas, non seulement vous vous retrouvez avec une exception trompeuse, mais elle n'est pas levée jusqu'à ce que potentiellement beaucoup plus tard que lorsque l'erreur réelle s'est produite.

En résumé:

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

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

  • Si vous devez traiter un objet en tant qu'instance d'un certain type et que l'objet est supposé être de ce type, utilisez une distribution standard.


39
2018-06-11 19:34



J'ai eu un Type-property à comparer à et ne pouvait pas utiliser is (comme my_type is _BaseTypetoLookFor), mais je pourrais utiliser ceux-ci:

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

Remarquerez que IsInstanceOfType et IsAssignableFrom revenir true lors de la comparaison des mêmes types, où IsSubClassOf retournera false. Et IsSubclassOf ne fonctionne pas sur les interfaces, où les deux autres font. (Voir également cette question et 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

11
2018-05-15 10:39



je préfère est

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

Supposons que Person: Entity et Animal: Entity. 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
  }
}

7
2018-06-11 19:15



Si vous utilisez C # 7, alors il est temps de mettre à jour la bonne réponse d'Andrew Hare. Correspondance de modèle a introduit un joli raccourci qui nous donne une variable typée dans le contexte de l'instruction if, sans nécessiter de déclaration / cast séparé et de vérifier:

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

Cela semble assez décevant pour un seul casting comme celui-ci, mais brille vraiment quand vous avez beaucoup de types possibles entrant dans votre routine. Le 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 autour de rétrécir ce code autant que possible, ainsi que d'é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 selon le commentaire de Palec.


6
2018-02-01 15:47



Je crois que le dernier regarde aussi l'héritage (par exemple Dog is Animal == true), ce qui est mieux dans la plupart des cas.


5
2018-06-11 19:14



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


2
2018-06-11 19:14