Verifica del tipo: typeof, GetType o è?


1513

Ho visto molte persone usare il seguente codice:

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

Ma so che potresti anche fare questo:

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

O questo:

if (obj1 is int)
    // Some code here

Personalmente, penso che l'ultimo sia il più pulito, ma c'è qualcosa che mi manca? Qual è il migliore da usare o è una preferenza personale?


28
Non dimenticare as!
RCIX,

82
asin realtà non è il controllo del tipo ...
Jasonh,

49
asè certamente una forma di controllo del tipo, tanto quanto lo isè! Si utilizza efficacemente isdietro le quinte e viene utilizzato ovunque in MSDN in luoghi in cui migliora la pulizia del codice rispetto is. Invece di controllare isprima, una chiamata per asstabilire una variabile digitata che è pronta per l'uso: se è nulla, rispondi in modo appropriato; altrimenti, procedere. Certamente qualcosa che ho visto e usato parecchio.
Zaccone,

15
Esiste una differenza significativa in termini di prestazioni a favore di as/ is(trattata in stackoverflow.com/a/27813381/477420 ) ipotizzando le sue opere semantiche per il tuo caso.
Alexei Levenkov,

@samusarin non "usa" la riflessione. Il GetTypemetodo a cui ci si collega è in System.Reflection.Assembly- un metodo completamente diverso e irrilevante qui.
Kirk Woll,

Risposte:


1849

Sono tutti diversi.

  • typeof prende un nome di tipo (che si specifica al momento della compilazione).
  • GetType ottiene il tipo di runtime di un'istanza.
  • is restituisce vero se un'istanza si trova nella struttura ereditaria.

Esempio

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

Che dire typeof(T)? Viene anche risolto in fase di compilazione?

Sì. T è sempre il tipo di espressione. Ricorda, un metodo generico è sostanzialmente un intero gruppo di metodi con il tipo appropriato. Esempio:

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"

29
Ah, quindi se ho una classe Ford che deriva da Car e un'istanza di Ford, controllare "is Car" su quell'istanza sarà vero. Ha senso!
Jasonh,

2
Per chiarire, ne ero consapevole, ma ho commentato prima di aggiungere un esempio di codice. Volevo provare ad aggiungere un po 'di chiarezza inglese alla tua già eccellente risposta.
Jasonh,

12
@Shimmy se typeof viene valutato in fase di compilazione e GetType () viene valutato in fase di esecuzione, quindi ha senso che GetType () incorra in un lieve calo delle prestazioni
Cedric Mamo,

che dire di nuovo Dog (). GetType () è Animal O typeof (Dog) è Animal, dà solo un avvertimento e non un errore?
Prerak K,

7
@PrerakK new Dog().GetType() is Animalrestituisce false (e anche l'altra versione) poiché .GetType()restituisce un oggetto di tipo Typee Typenon è un Animal.
Maarten,

195

Utilizzare typeofquando si desidera ottenere il tipo al momento della compilazione . Utilizzare GetTypequando si desidera ottenere il tipo al momento dell'esecuzione . Raramente ci sono casi da usare iscome fa un cast e, nella maggior parte dei casi, finisci per lanciare comunque la variabile.

C'è una quarta opzione che non hai preso in considerazione (specialmente se stai per lanciare un oggetto sul tipo che trovi anche); quello è usare as.

Foo foo = obj as Foo;

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

Questo utilizza solo un cast mentre questo approccio:

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

richiede due .

Aggiornamento (gennaio 2020):

  • A partire da C # 7+ , ora puoi lanciare in linea, quindi l'approccio 'is' ora può essere eseguito anche in un cast.

Esempio:

if(obj is Foo newLocalFoo)
{
    // For example, you can now reference 'newLocalFoo' in this local scope
    Console.WriteLine(newLocalFoo);
}

4
Con le modifiche in .NET 4 esegue isancora un cast?
ahsteele,

6
Questa risposta è corretta? È vero che puoi davvero passare un'istanza in typeof ()? La mia esperienza è stata No. Ma immagino sia generalmente vero che il controllo di un'istanza potrebbe essere necessario in fase di esecuzione, mentre il controllo di una classe dovrebbe essere fattibile al momento della compilazione.
Jon Coombs,

4
@jon (4 anni dopo la tua q.), no, non puoi passare un'istanza in typeof(), e questa risposta non suggerisce che puoi. Si passa invece al tipo, ovvero typeof(string)funziona, typeof("foo")no.
Abel,

Non credo che isesegua il cast in quanto tale, un'operazione piuttosto speciale in IL.
abatishchev,

3
Ora possiamo fareif (obj is Foo foo) { /* use foo here */ }
Ivan García Topete il

71

1.

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

Questo è illegale, perché typeoffunziona solo sui tipi, non sulle variabili. Presumo che obj1 sia una variabile. Quindi, in questo modo typeofè statico e fa il suo lavoro in fase di compilazione anziché in fase di esecuzione.

2.

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

Questo è truese obj1è esattamente di tipo int. Se obj1deriva da int, la condizione if sarà false.

3.

if (obj1 is int)

Questo è trueif obj1is an int, oppure se deriva da una classe chiamata into se implementa un'interfaccia chiamata int.


Pensando a 1, hai ragione. Eppure, l'ho visto in diversi esempi di codice qui. Dovrebbe essere Type t = obj1.GetType ();
Jasonh,

4
Sì, penso di si. "typeof (obj1)" non viene compilato quando lo provo.
Scott Langham,

4
È impossibile derivare da System.Int32 o da qualsiasi altro tipo di valore in C #
reggaeguitar il

puoi dire quale sarebbe typeof (typeof (system.int32))
Sana,

1
@Sana, perché non lo provi :) Immagino che anche se torni un'istanza di System.Type che rappresenta il tipo System.Type! La documentazione per typeof è qui: docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
Scott Langham,

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

Questo è un errore L'operatore typeof in C # può accettare solo nomi di tipi, non oggetti.

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

Funzionerà, ma forse non come ti aspetteresti. Per i tipi di valore, come mostrato qui, è accettabile, ma per i tipi di riferimento, restituisce true solo se il tipo era dello stesso tipo esatto , non qualcos'altro nella gerarchia dell'ereditarietà. Per esempio:

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

Questo verrebbe stampato "o is something else", perché il tipo di oè Dog, non Animal. Puoi farlo funzionare, tuttavia, se usi il IsAssignableFrommetodo della Typeclasse.

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

Questa tecnica lascia comunque un grosso problema. Se la variabile è nulla, la chiamata a GetType()genererà una NullReferenceException. Quindi, per farlo funzionare correttamente, dovresti fare:

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

Con questo, hai un comportamento equivalente della isparola chiave. Pertanto, se questo è il comportamento desiderato, è necessario utilizzare la isparola chiave, che è più leggibile e più efficiente.

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

Nella maggior parte dei casi, tuttavia, la isparola chiave non è ancora ciò che desideri davvero, perché di solito non è sufficiente sapere che un oggetto è di un certo tipo. Di solito, si desidera effettivamente utilizzare quell'oggetto come istanza di quel tipo, il che richiede anche il cast. E così potresti ritrovarti a scrivere codice in questo modo:

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

Ma ciò fa in modo che il CLR controlli il tipo di oggetto fino a due volte. Lo controllerà una volta per soddisfare l' isoperatore, e se oè effettivamente un Animal, lo faremo di nuovo per convalidare il cast.

È invece più efficiente farlo:

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

L' asoperatore è un cast che non genererà un'eccezione se fallisce, invece ritorna null. In questo modo, il CLR controlla il tipo di oggetto solo una volta e, successivamente, dobbiamo solo fare un controllo nullo, che è più efficiente.

Ma attenzione: molte persone cadono in una trappola as. Poiché non genera eccezioni, alcune persone lo considerano un cast "sicuro" e lo usano esclusivamente, evitando lanci regolari. Questo porta a errori come questo:

(o as Animal).Speak();

In questo caso, lo sviluppatore presuppone chiaramente che osarà sempre un Animal, e fintanto che la sua ipotesi è corretta, tutto funziona bene. Ma se si sbagliano, allora quello che finiscono qui è a NullReferenceException. Con un cast regolare, avrebbero ottenuto un InvalidCastExceptioninvece, che avrebbe identificato più correttamente il problema.

A volte, questo bug può essere difficile da trovare:

class Foo{
    readonly Animal animal;

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

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

Questo è un altro caso in cui lo sviluppatore si aspetta chiaramente odi esserlo Animalogni volta, ma questo non è ovvio nel costruttore, in cui asviene utilizzato il cast. Non è ovvio finché non si arriva al Interactmetodo, dove animalsi prevede che il campo verrà assegnato in modo positivo. In questo caso, non solo si ottiene un'eccezione fuorviante, ma non viene generata fino a quando potenzialmente si è verificato molto più tardi di quando si è verificato l'errore effettivo.

In sintesi:

  • Se hai solo bisogno di sapere se un oggetto è di qualche tipo, usa is.

  • Se devi trattare un oggetto come un'istanza di un certo tipo, ma non sai con certezza che l'oggetto sarà di quel tipo, usa ase controlla null.

  • Se devi trattare un oggetto come un'istanza di un certo tipo e si suppone che l'oggetto sia di quel tipo, usa un cast regolare.


cosa c'è di sbagliato in questo se (o è Animal) ((Animal) o) .Speak (); ? puoi per favore fornire maggiori dettagli?
Batmaci,

2
@batmaci: è nella risposta - provoca due controlli di tipo. La prima volta è o is Animal, che richiede al CLR di verificare se il tipo di variabile oè un Animal. La seconda volta che controlla è quando lancia nell'istruzione ((Animal)o).Speak(). Invece di controllare due volte, controlla una volta usando as.
siride,

Ho trovato questa una spiegazione assolutamente fantastica, grazie per il chiarimento!
Paul Efford,

16

Se stai usando C # 7, allora è tempo di aggiornare la grande risposta di Andrew Hare. La corrispondenza dei modelli ha introdotto una bella scorciatoia che ci fornisce una variabile tipizzata nel contesto dell'istruzione if, senza richiedere una dichiarazione / cast separata e controllare:

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

Sembra abbastanza deludente per un singolo cast come questo, ma brilla davvero quando hai molti tipi possibili che entrano nella tua routine. Di seguito è riportato il vecchio modo per evitare di lanciare due volte:

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

Lavorare per ridurre il più possibile questo codice, ed evitare lanci duplicati dello stesso oggetto mi ha sempre infastidito. Quanto sopra è ben compresso con pattern matching al seguente:

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

EDIT: aggiornato il nuovo metodo più lungo per utilizzare un interruttore secondo il commento di Palec.


1
In questo caso è consigliabile usare l' switchistruzione con la corrispondenza del modello .
Palec,

Come faresti con un non lo è? In questo particolare blocco di codice? if (obj1 is int integerValue) { integerValue++; }
Ben Vertonghen,

Ben, se capisco la tua domanda, avrei solo un'istruzione else per gestire altri casi poiché non puoi inserire un non intero in una variabile intera. :)
JoelC

14

Ho avuto una Type proprietà da confrontare e non potevo usare is(come my_type is _BaseTypetoLookFor), ma potevo usare questi:

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

Notare che IsInstanceOfTypee IsAssignableFromrestituire truequando si confrontano gli stessi tipi, in cui verrà restituito IsSubClassOf false. E IsSubclassOfnon funziona su interfacce, come fanno gli altri due. (Vedi anche questa domanda e risposta .)

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

9

Io preferisco IS

Detto questo, se si sta utilizzando è , è molto probabile che non utilizza l'ereditarietà correttamente.

Assumi quella persona: Entità e quell'animale: Entità. Il feed è un metodo virtuale in Entity (per rendere felice 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
    }
  }
}

Piuttosto

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

1
È vero, non farei mai il primo, sapendo che la persona deriva dall'animale.
Jasonh,

3
Quest'ultimo non sta nemmeno usando l'ereditarietà. Foo dovrebbe essere un metodo virtuale di Entità che viene sovrascritto in Persona e Animale.
Neil Williams,

2
@bobobobo Penso che intendi "sovraccarico", non "eredità".
lc.

@lc: No, intendo l'eredità. Il primo esempio è una specie di modo errato (usare is ) per ottenere comportamenti diversi. Il secondo esempio usa l'overloading di yes, ma evita l'uso di is .
Bobobobo,

1
Il problema con l'esempio è che non si ridimensionerebbe. Se hai aggiunto nuove entità che avevano bisogno di mangiare (ad esempio un Insetto o un Mostro), dovrai aggiungere un nuovo metodo nella classe Entità e quindi sovrascriverlo in sottoclassi che lo alimenterebbero. Questo non è più preferibile di un elenco se (entità è X) altrimenti se (entità è Y) ... Ciò viola LSP e OCP, l'ereditarietà probabilmente non è la soluzione migliore al problema. Probabilmente sarebbe preferibile una qualche forma di delega.
Ebrown,

5

Credo che l'ultimo consideri anche l'ereditarietà (ad es. Dog is Animal == true), che è meglio nella maggior parte dei casi.


2

Dipende da cosa sto facendo. Se ho bisogno di un valore bool (diciamo, per determinare se eseguirò il cast in un int), userò is. Se in realtà ho bisogno del tipo per qualche motivo (diciamo, per passare ad un altro metodo) lo userò GetType().


1
Buon punto. Ho dimenticato di dire che sono arrivato a questa domanda dopo aver esaminato diverse risposte che utilizzavano un'istruzione if per verificare un tipo.
Jasonh,

0

L'ultimo è più pulito, più ovvio e controlla anche i sottotipi. Gli altri non controllano il polimorfismo.


0

Utilizzato per ottenere l'oggetto System.Type per un tipo. Un'espressione typeof ha la forma seguente:

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
    */

In questo esempio viene utilizzato il metodo GetType per determinare il tipo utilizzato per contenere il risultato di un calcolo numerico. Ciò dipende dai requisiti di archiviazione del numero risultante.

    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
    */

-4
if (c is UserControl) c.Enabled = enable;

4
Modifica con ulteriori informazioni. Le risposte di solo codice e "prova questo" sono scoraggiate, perché non contengono contenuti ricercabili e non spiegano perché qualcuno dovrebbe "provare questo".
Abarisone,

La tua risposta non è correlata alla domanda.
menxin,

-5

È possibile utilizzare l'operatore "typeof ()" in C # ma è necessario chiamare lo spazio dei nomi utilizzando System.IO; È necessario utilizzare la parola chiave "is" se si desidera verificare un tipo.


7
typeofnon è definito in uno spazio dei nomi, è una parola chiave. System.IOnon ha nulla a che fare con questo.
Arturo Torres Sánchez,

-5

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

Risultati in modalità debug:

00:00:08.4096636
00:00:10.8570657

Risultati in modalità di rilascio:

00:00:02.3799048
00:00:07.1797128

1
Non si dovrebbe usare DateTime.UtcNow per le misure delle prestazioni. Con il tuo codice ma con la classe Stopwatch ho ottenuto risultati costantemente opposti per la modalità Debug. UseTypeOf: 00: 00: 14.5074469 UseGetType: 00: 00: 10.5799534. La modalità di rilascio è la stessa del previsto
Alexey Shcherbak il

@AlexeyShcherbak La differenza tra Cronometro e DateTime.Now non può essere superiore a 10-20 ms, controlla di nuovo il codice. E non mi importa dei millisecondi nel mio test. Inoltre il mio codice sarà più lungo con diverse righe di codice con Cronometro.
Alexander Vasilyev,

1
è una cattiva pratica in generale, non nel tuo caso particolare.
Alexey Shcherbak,

4
@AlexanderVasilyev La quantità di righe di codice non dovrebbe mai essere usata come argomento per fare qualcosa di documentato fuorviante. Come visto in msdn.microsoft.com/en-us/library/system.datetime(v=vs.110).aspx , DateTimenon dovrebbe essere usato se sei preoccupato per tempi inferiori a 100ms , poiché utilizza il periodo di tempo del sistema operativo. Rispetto a Stopwatch, che utilizza i processori Tick, la risoluzione utilizzata da un DateTimein Win7 è un enorme 15ms.
Eric Wu
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.