Vérification de type: typeof, GetType, ou est?

1513

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 peux aussi faire ça:

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?

jasonh
la source
28
N'oubliez pas as!
RCIX
82
asn'est pas vraiment la vérification de type si ...
jasonh
49
asest certainement une forme de vérification de type, autant que possible is! Il utilise efficacement isles coulisses et est utilisé partout dans MSDN dans les endroits où il améliore la propreté du code par rapport à is. Au lieu de vérifier d' isabord, un appel pour asétablir une variable typée prête à l'emploi: si elle est nulle, répondez de manière appropriée; sinon, continuez. Certainement quelque chose que j'ai vu et utilisé un peu.
Zaccone
15
Il existe une différence de performances significative en faveur de as/ is(couvert dans stackoverflow.com/a/27813381/477420 ) en supposant que ses travaux sémantiques s'appliquent à votre cas.
Alexei Levenkov
@samusarin il n'utilise pas la réflexion. La GetTypeméthode à laquelle vous vous connectez est System.Reflection.Assembly- une méthode complètement différente et non pertinente ici.
Kirk Woll

Réponses:

1850

Tous sont différents.

  • typeof prend un nom de type (que vous spécifiez au moment de 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);

Et alors typeof(T)? Est-il également résolu au moment de la compilation?

Oui. T est toujours le type de l'expression. N'oubliez pas qu'une méthode générique est essentiellement 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"
Jimmy
la source
29
Ah, donc si j'ai une classe Ford qui dérive de Car et une instance de Ford, vérifier "is Car" sur cette instance sera vrai. Logique!
jasonh
2
Pour clarifier, j'étais conscient de cela, mais j'ai commenté avant d'ajouter un exemple de code. Je voulais essayer d'ajouter une certaine clarté anglaise à votre réponse déjà excellente.
jasonh
12
@Shimmy si typeof est évalué au moment de la compilation et GetType () est évalué au moment de l'exécution, alors il est logique que GetType () entraîne un léger impact sur les performances
Cedric Mamo
Qu'en est-il du nouveau chien (). GetType () est animal ou typeof (chien) est animal, il donne juste un avertissement et non une erreur?
Prerak K
7
@PrerakK new Dog().GetType() is Animalrenvoie false (et votre autre version également) car .GetType()retourne un objet de type Type, et Typen'est pas un Animal.
Maarten du
195

À utiliser typeoflorsque vous souhaitez obtenir le type au moment de la compilation . À utiliser GetTypelorsque vous souhaitez obtenir le type au moment de l'exécution . Il y a rarement des cas à utiliser iscar il fait un cast et, dans la plupart des cas, vous finissez par caster la variable de toute façon.

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

Foo foo = obj as Foo;

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

Cela n'utilise qu'un seul cast alors que cette approche:

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

nécessite deux .

Mise à jour (janvier 2020):

  • À partir de C # 7+ , vous pouvez désormais effectuer un cast en ligne, de sorte que l'approche «est» peut désormais être effectuée en un seul casting.

Exemple:

if(obj is Foo newLocalFoo)
{
    // For example, you can now reference 'newLocalFoo' in this local scope
    Console.WriteLine(newLocalFoo);
}
Andrew Hare
la source
4
Avec les changements dans .NET 4, iseffectue- t-il toujours un cast?
ahsteele
6
Cette réponse est-elle correcte? Est-il vrai que vous pouvez vraiment passer une instance dans typeof ()? Mon expérience a été non. Mais je suppose qu'il est généralement vrai que la vérification d'une instance peut se produire au moment de l'exécution, tandis que la vérification d'une classe devrait être faisable au moment de la compilation.
Jon Coombs
4
@jon (4 ans après votre q.), non, vous ne pouvez pas passer une instance en typeof(), et cette réponse ne suggère pas que vous pouvez. Vous passez le type à la place, c'est-à-dire que typeof(string)fonctionne, typeof("foo")ne fonctionne pas.
Abel
Je ne crois pas que le iscasting soit tel, une opération plutôt spéciale en IL.
abatishchev
3
Nous pouvons maintenant le faireif (obj is Foo foo) { /* use foo here */ }
Ivan García Topete
71

1.

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

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

2.

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

C'est truesi obj1est exactement de type int. Si obj1dérive de int, la condition if sera false.

3.

if (obj1 is int)

C'est truesi obj1est un int, ou s'il dérive d'une classe appelée int, ou s'il implémente une interface appelée int.

Scott Langham
la source
En pensant à 1, vous avez raison. Et pourtant, je l'ai vu dans plusieurs exemples de code ici. Ce doit être Type t = obj1.GetType ();
jasonh
4
Oui, je pense que oui. "typeof (obj1)" ne se compile pas lorsque je l'essaye.
Scott Langham
4
Il est impossible de dériver de System.Int32 ou de tout autre type de valeur en C #
reggaeguitar
pouvez-vous dire quel serait typeof (typeof (system.int32))
Sana
1
@Sana, pourquoi ne pas l'essayer :) J'imagine que vous récupérez une instance de System.Type qui représente le type System.Type! La documentation pour typeof est ici: docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
Scott Langham
53
Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

Ceci est une erreur. L'opérateur typeof en C # ne peut prendre que 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 exactement le 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 s'imprimerait "o is something else", car le type de oest Dognon Animal. Vous pouvez cependant faire ce travail si vous utilisez la IsAssignableFromméthode de la Typeclasse.

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 nulle, l'appel à GetType()lèvera 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 du ismot - clé. Par conséquent, si tel est le comportement que vous souhaitez, vous devez utiliser le ismot - 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 ismot - clé n'est toujours pas ce que vous voulez vraiment, car il ne suffit généralement pas de savoir qu'un objet est d'un certain type. Habituellement, vous souhaitez réellement utiliser cet objet en tant qu'instance de ce type, ce qui nécessite également de le convertir. Et vous pouvez donc vous retrouver à écrire du code comme ceci:

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

Mais cela oblige le CLR à vérifier le type de l'objet jusqu'à deux fois. Il le vérifiera une fois pour satisfaire l' isopérateur, et s'il os'agit bien d'un Animal, on le refait vérifier pour valider le cast.

Il est plus efficace de le faire à la place:

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

L' asopérateur est un transtypage qui ne lèvera pas d'exception s'il échoue, au lieu de cela null. De cette façon, le CLR vérifie le type de l'objet une seule 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 lève pas d'exceptions, certaines personnes le considèrent comme 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 ce osera toujours un Animal, et tant que leur hypothèse est correcte, tout fonctionne bien. Mais s'ils se trompent, ce qu'ils obtiennent ici est un NullReferenceException. Avec un casting régulier, ils auraient obtenu un à la InvalidCastExceptionplace, ce qui aurait identifié plus correctement 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();
    }
}

C'est un autre cas où le développeur s'attend clairement oà être à Animalchaque fois, mais ce n'est pas évident dans le constructeur, où la asdistribution est utilisée. Ce n'est pas évident jusqu'à ce que vous arriviez à la Interactméthode, où le animalchamp devrait être attribué positivement. Dans ce cas, non seulement vous vous retrouvez avec une exception trompeuse, mais elle n'est levée que bien 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 vous ne savez pas avec certitude si l'objet sera de ce type, utilisez-le aset vérifiez-le null.

  • Si vous devez traiter un objet comme une instance d'un certain type, et que l'objet est censé être de ce type, utilisez une conversion régulière.

P papa
la source
qu'est-ce qui ne va pas si (o est Animal) ((Animal) o) .Speak (); ? pouvez-vous donner plus de détails?
batmaci
2
@batmaci: c'est dans la réponse - cela provoque deux vérifications de type. La première fois o is Animal, ce qui nécessite que le CLR vérifie si le type de la variable oest un Animal. La deuxième fois qu'il vérifie, c'est quand il transforme la déclaration ((Animal)o).Speak(). Plutôt que de vérifier deux fois, vérifiez une fois en utilisant as.
siride
J'ai trouvé cela une explication absolument géniale, merci pour la clarification!
Paul Efford
16

Si vous utilisez C # 7, il est temps de mettre à jour la grande réponse d'Andrew Hare. La correspondance de modèles 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 / distribution distincte et de vérifier:

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

Cela semble assez décevant pour une distribution unique comme celle-ci, mais brille vraiment lorsque vous avez de nombreux types possibles dans votre routine. Ce qui suit 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éduire autant que possible ce code, ainsi que d'éviter les doublons de cast du même objet m'a toujours dérangé. Ce qui précède est bien compressé avec un motif correspondant aux éléments suivants:

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.

JoelC
la source
1
Dans ce cas, l' utilisation d'une switchinstruction avec correspondance de motifs est recommandée.
Palec
Comment feriez-vous face à un non? Dans ce bloc de code particulier? if (obj1 is int integerValue) { integerValue++; }
Ben Vertonghen
Ben, si je comprends votre question, j'aurais juste une instruction else pour gérer les autres cas puisque vous ne pouvez pas mettre un entier non dans une variable entière. :)
JoelC
14

J'avais une Typepropriété à 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 cela IsInstanceOfTypeet IsAssignableFromretournez truelorsque vous comparez les mêmes types, où IsSubClassOf retournera false. Et IsSubclassOfne fonctionne pas sur les interfaces, où les deux autres le font. (Voir aussi 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
Yahoo Serious
la source
9

Je préfère est

Cela dit, si vous utilisez quels , vous risquez pas utiliser l' héritage correctement.

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

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
  }
}
bobobobo
la source
1
Certes, je ne ferais jamais le premier, sachant que la personne dérive de l'animal.
jasonh
3
Ce dernier n'utilise pas vraiment l'héritage non plus. Foo doit être une méthode virtuelle d'entité qui est remplacée dans Person and Animal.
Neil Williams
2
@bobobobo Je pense que vous voulez dire "surcharge", pas "héritage".
lc.
@lc: Non, je veux dire l'héritage. Le premier exemple est une sorte de méthode incorrecte (en utilisant is ) pour obtenir un comportement différent. Le deuxième exemple utilise la surcharge oui, mais évite d'utiliser is .
bobobobo
1
Le problème avec l'exemple est qu'il ne serait pas évolutif. Si vous avez ajouté de nouvelles entités qui devaient manger (par exemple un insecte ou un monstre), vous devez ajouter une nouvelle méthode dans la classe Entity, puis la remplacer dans les sous-classes qui l'alimenteront. Ce n'est pas plus préférable qu'une liste si (l'entité est X) sinon si (l'entité est Y) ... Cela viole le LSP et l'OCP, l'héritage n'est probablement pas la meilleure solution au problème. Une forme de délégation serait probablement préférable.
ebrown
5

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

StriplingWarrior
la source
2

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 transtyper en entier), je l'utilise is. Si j'ai besoin du type pour une raison quelconque (par exemple, pour passer à une autre méthode), je l'utilise GetType().

AllenG
la source
1
Bon point. J'ai oublié de mentionner que je suis arrivé à cette question après avoir examiné plusieurs réponses qui utilisaient une instruction if pour vérifier un type.
jasonh
0

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

thecoop
la source
0

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 exigences 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
    */
Muhammad Awais
la source
-4
if (c is UserControl) c.Enabled = enable;
Paulos02
la source
4
Veuillez modifier avec plus d'informations. Les réponses codées uniquement et "essayez ceci" sont déconseillées, car elles ne contiennent aucun contenu consultable et n'expliquent pas pourquoi quelqu'un devrait "essayer ceci".
abarisone
Votre réponse n'est pas liée à la question.
menxin
-5

Vous pouvez utiliser l'opérateur "typeof ()" en C # mais vous devez appeler l'espace de noms à l'aide de System.IO; Vous devez utiliser le mot clé "is" si vous souhaitez rechercher un type.

androidrill
la source
7
typeofn'est pas défini dans un espace de noms, c'est un mot-clé. System.IOn'a rien à voir avec cela.
Arturo Torres Sánchez
-5

Test de performance typeof () vs GetType ():

using System;
namespace ConsoleApplication1
    {
    class Program
    {
        enum TestEnum { E1, E2, E3 }
        static void Main(string[] args)
        {
            {
                var start = DateTime.UtcNow;
                for (var i = 0; i < 1000000000; i++)
                    Test1(TestEnum.E2);
                Console.WriteLine(DateTime.UtcNow - start);
            }
            {
                var start = DateTime.UtcNow;
                for (var i = 0; i < 1000000000; i++)
                    Test2(TestEnum.E2);
                Console.WriteLine(DateTime.UtcNow - start);
            }
            Console.ReadLine();
        }
        static Type Test1<T>(T value) => typeof(T);
        static Type Test2(object value) => value.GetType();
    }
}

Résultats en mode débogage:

00:00:08.4096636
00:00:10.8570657

Résultats en mode release:

00:00:02.3799048
00:00:07.1797128
Alexander Vasilyev
la source
1
Il ne faut pas utiliser DateTime.UtcNow pour les mesures de performances. Avec votre code mais avec la classe Stopwatch, j'ai obtenu des résultats constamment opposés pour le mode Debug. UseTypeOf: 00: 00: 14.5074469 UseGetType: 00: 00: 10.5799534. Le mode de sortie est le même, comme prévu
Alexey Shcherbak
@AlexeyShcherbak La différence entre le chronomètre et DateTime.Now ne peut pas être supérieure à 10-20 ms, vérifiez à nouveau votre code. Et je me fiche des millisecondes dans mon test. De plus, mon code comportera plusieurs lignes de code plus longtemps avec Chronomètre.
Alexander Vasilyev,
1
c'est une mauvaise pratique en général, pas dans votre cas particulier.
Alexey Shcherbak
4
@AlexanderVasilyev Montant des lignes de code ne doit jamais être utilisé comme argument pour faire quelque chose documentedly trompeur. Comme on le voit dans msdn.microsoft.com/en-us/library/system.datetime(v=vs.110).aspx , DateTimene devrait pas être utilisé si vous êtes préoccupé par les temps ci - dessous 100ms , car il utilise le calendrier du système d' exploitation. Comparativement à Stopwatch, qui utilise les processeurs Tick, la résolution utilisée par un DateTimedans Win7 est une coqueluche de 15 ms.
Eric Wu