Tipo di controllo: typeof, GetType o is?

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, sento che l’ultimo è il più pulito, ma c’è qualcosa che mi manca? Qual è il migliore da usare, o è una preferenza personale?

Tutti sono diversi

  • typeof accetta un nome di tipo (che si specifica al momento della compilazione).
  • GetType ottiene il tipo di runtime di un’istanza.
  • is restituisce true se un’istanza si trova nell’albero di ereditarietà.

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 ne dici di typeof(T) ? È risolto anche in fase di compilazione?

Sì. T è sempre ciò che è il tipo di espressione. Ricorda, un metodo generico è fondamentalmente un insieme di metodi con il tipo appropriato. Esempio:

 string Foo(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 and returns "Animal" Foo(probably_a_dog); // this is exactly the same as above Foo(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 and returns "Dog" Foo(definitely_a_dog); // this is exactly the same as above. Foo(definitely_a_dog); // this calls Foo and returns "Animal". Foo((Animal)definitely_a_dog); // this does the same as above, returns "Animal" 

Usa typeof quando vuoi ottenere il tipo al momento della compilazione . Usa GetType quando vuoi ottenere il tipo al momento dell’esecuzione . Raramente ci sono casi da usare come fa il cast e, nella maggior parte dei casi, si finisce col castare comunque la variabile.

C’è una quarta opzione che non hai considerato (specialmente se hai intenzione di lanciare un object per il tipo che trovi anche tu); quello è usare as

 Foo foo = obj as Foo; if (foo != null) // your code here 

Questo utilizza solo un cast considerando questo approccio:

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

ne richiede due .

1.

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

Questo è illegale, perché typeof funziona 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 runtime.

2.

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

Questo è vero se obj1 è esattamente di tipo int. Se obj1 deriva da int, la condizione if sarà falsa.

3.

 if (obj1 is int) 

Questo è vero se obj1 è un int, o se deriva da una class chiamata int, o se implementa un’interfaccia chiamata int.

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

Questo è un errore. L’operatore typeof in C # può solo prendere 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 hai mostrato qui, è accettabile, ma per i tipi di riferimento, restituirebbe true solo se il tipo era esattamente lo stesso tipo, 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 stamperebbe "o is something else" , perché il tipo di o è Dog , non Animal . Puoi farlo funzionare, tuttavia, se utilizzi il metodo IsAssignableFrom della class Type .

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

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

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

Con questo, si ha un comportamento equivalente della parola chiave is . Quindi, se questo è il comportamento che si desidera, è necessario utilizzare la parola chiave is , che è più leggibile e più efficiente.

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

Nella maggior parte dei casi, tuttavia, la parola chiave is ancora non è ciò che si desidera realmente, perché in genere non è sufficiente sapere che un object è di un certo tipo. Di solito, vuoi effettivamente usare quell’object come un’istanza di quel tipo, che richiede anche il cast. E quindi potresti trovarti a scrivere un codice come questo:

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

Ma ciò fa sì che il CLR controlli il tipo dell’object fino a due volte. Lo controllerà una volta per soddisfare l’operatore di is , e se o è davvero un Animal , lo facciamo di nuovo controllare per convalidare il cast.

È più efficiente farlo invece:

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

L’operatore as è un cast che non genererà un’eccezione se fallisce, restituendo invece null . In questo modo, il CLR controlla il tipo dell’object solo una volta, e dopo questo, abbiamo solo bisogno di fare un controllo nullo, che è più efficiente.

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

 (o as Animal).Speak(); 

In questo caso, lo sviluppatore sta chiaramente supponendo che o sarà sempre un Animal , e finché la loro ipotesi è corretta, tutto funziona correttamente. Ma se hanno torto, allora quello che finiscono qui è una NullReferenceException . Con un lancio regolare, avrebbero invece ottenuto una InvalidCastException , 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 di essere un Animal ogni volta, ma questo non è ovvio nel costruttore, dove viene utilizzato il cast. Non è ovvio fino a quando non si arriva al metodo Interact , dove ci si aspetta che il campo animal venga assegnato in modo positivo. In questo caso, non solo si finisce con un’eccezione fuorviante, ma non viene generata fino a quando non molto più tardi rispetto a quando si è verificato l’errore effettivo.

In sintesi:

  • Se hai solo bisogno di sapere se un object è o meno di qualche tipo, l’uso is .

  • Se devi trattare un object come un’istanza di un certo tipo, ma non sai per certo che l’object sarà di quel tipo, usa as e controlla null .

  • Se devi trattare un object come un’istanza di un certo tipo e l’object dovrebbe essere di quel tipo, usa un cast normale.

Avevo una proprietà Type da confrontare e non poteva usare is (come my_type is _BaseTypetoLookFor ), ma potrei usare questi:

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

Si noti che IsInstanceOfType e IsAssignableFrom restituiscono true quando si confrontano gli stessi tipi, in cui IsSubClassOf restituirà false . E IsSubclassOf non funziona sulle interfacce, dove le altre due lo fanno. (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 

Preferisco lo è

Detto questo, se stai usando, probabilmente non usi correttamente l’ereditarietà.

Supponi che Person: Entity e that Animal: Entity. Feed è un metodo virtuale in Entity (per rendere Neil felice)

 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 } } 

Se stai usando C # 7, allora è il momento di aggiornare la grande risposta di Andrew Hare. La corrispondenza dei pattern 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++; } 

Questo sembra piuttosto travolgente per un cast singolo come questo, ma brilla davvero quando hai molti tipi possibili che entrano nella tua routine. Il sotto è il vecchio modo per evitare di trasmettere 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ù ansible questo codice, oltre a evitare duplicati dello stesso object 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 come da commento di Palec.

Credo che l’ultimo guardi anche all’eredità (es. Dog is Animal == true), che è migliore nella maggior parte dei casi.

Dipende da cosa sto facendo. Se ho bisogno di un valore di bool (per esempio, per determinare se scriverò a un int), userò is . Se effettivamente ho bisogno del tipo per qualche motivo (per esempio, per passare ad un altro metodo) userò GetType() .

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

Utilizzato per ottenere l’object System.Type per un tipo. Un tipo di espressione assume la seguente forma:

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

Questo esempio utilizza il metodo GetType per determinare il tipo utilizzato per contenere il risultato di un calcolo numerico. Questo 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 */ 
 if (c is UserControl) c.Enabled = enable; 

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

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 value) => typeof(T); static Type Test2(object value) => value.GetType(); } } 

Risultati in modalità di debug:

 00:00:08.4096636 00:00:10.8570657 

Risultati in modalità di rilascio:

 00:00:02.3799048 00:00:07.1797128