Tipo de comprobación: typeof, GetType, o es?

He visto a mucha gente usar el siguiente código:

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

Pero sé que también puedes hacer esto:

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

O esto:

 if (obj1 is int) // Some code here 

Personalmente, creo que el último es el más limpio, pero ¿hay algo que me falta? ¿Cuál es el mejor para usar, o es una preferencia personal?

Todos son diferentes

  • typeof toma un nombre de tipo (que especifique en tiempo de comstackción).
  • GetType obtiene el tipo de tiempo de ejecución de una instancia.
  • devuelve verdadero si una instancia está en el árbol de herencia.

Ejemplo

 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é pasa con typeof(T) ? ¿También se resuelve en tiempo de comstackción?

Sí. T es siempre el tipo de expresión. Recuerde, un método genérico es básicamente un montón de métodos con el tipo apropiado. Ejemplo:

 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" 

Use typeof cuando desee obtener el tipo en tiempo de comstackción . Use GetType cuando desee obtener el tipo en el momento de la ejecución . Rara vez hay casos para usar como un yeso y, en la mayoría de los casos, terminas emitiendo la variable de todos modos.

Hay una cuarta opción que no ha considerado (especialmente si va a lanzar un objeto al tipo que encuentre también); eso es para usar as .

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

Esto solo usa un lanzamiento mientras que este enfoque:

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

requiere dos .

1.

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

Esto es ilegal, porque typeof solo funciona en tipos, no en variables. Supongo que obj1 es una variable. Entonces, de esta forma, typeof es estático y funciona en tiempo de comstackción en lugar de en tiempo de ejecución.

2.

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

Esto es cierto si obj1 es exactamente de tipo int. Si obj1 deriva de int, la condición if será falsa.

3.

 if (obj1 is int) 

Esto es cierto si obj1 es un int, o si se deriva de una clase llamada int, o si implementa una interfaz llamada int.

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

Esto es un error El operador de typeof en C # solo puede tomar nombres de tipos, no objetos.

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

Esto funcionará, pero tal vez no como cabría esperar. Para los tipos de valor, como ha mostrado aquí, es aceptable, pero para los tipos de referencia, solo devolvería verdadero si el tipo era exactamente el mismo tipo, no algo más en la jerarquía de herencia. Por ejemplo:

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

Esto imprimiría "o is something else" , porque el tipo de o es Dog , no Animal . Sin embargo, puede hacer que esto funcione si usa el método IsAssignableFrom de la clase Type .

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

Sin embargo, esta técnica aún deja un gran problema. Si su variable es nula, la llamada a GetType() arrojará una NullReferenceException. Entonces, para que funcione correctamente, harías:

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

Con esto, tiene un comportamiento equivalente de la palabra clave is . Por lo tanto, si este es el comportamiento que desea, debe usar la palabra clave is , que es más legible y más eficiente.

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

Sin embargo, en la mayoría de los casos, la palabra clave is todavía no es lo que realmente desea, porque generalmente no es suficiente solo para saber que un objeto es de cierto tipo. Por lo general, desea utilizar ese objeto como una instancia de ese tipo, que también requiere que se lance. Y entonces puede que te encuentres escribiendo código como este:

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

Pero eso hace que el CLR verifique el tipo del objeto hasta dos veces. Lo comprobará una vez para satisfacer al operador is , y si o es un Animal , hacemos que vuelva a verificar para validar el lanzamiento.

Es más eficiente hacer esto en su lugar:

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

El operador as es un molde que no lanzará una excepción si falla, sino que devuelve null . De esta forma, el CLR verifica el tipo del objeto solo una vez, y después de eso, solo tenemos que hacer una comprobación nula, que es más eficiente.

Pero ten cuidado: muchas personas caen en una trampa con as . Debido a que no arroja excepciones, algunas personas piensan que es un yeso “seguro” y lo usan exclusivamente, evitando los lanzamientos normales. Esto lleva a errores como este:

 (o as Animal).Speak(); 

En este caso, el desarrollador está asumiendo claramente que o siempre será un Animal , y siempre que su suposición sea correcta, todo funciona bien. Pero si están equivocados, entonces lo que terminan aquí es una NullReferenceException . Con un elenco regular, en cambio tendrían una InvalidCastException , que habría identificado el problema más correctamente.

A veces, este error puede ser difícil de encontrar:

 class Foo{ readonly Animal animal; public Foo(object o){ animal = o as Animal; } public void Interact(){ animal.Speak(); } } 

Este es otro caso donde el desarrollador claramente espera ser un Animal cada vez, pero esto no es obvio en el constructor, donde se usa el yeso. No es obvio hasta que llegue al método de Interact , donde se espera que el campo de los animal se asigne positivamente. En este caso, no solo termina con una excepción engañosa, sino que no se lanza hasta mucho más tarde que cuando ocurrió el error real.

En resumen:

  • Si solo necesita saber si un objeto es de algún tipo, use is .

  • Si necesita tratar un objeto como una instancia de cierto tipo, pero no está seguro de que el objeto sea de ese tipo, use as y busque null .

  • Si necesita tratar un objeto como una instancia de cierto tipo, y se supone que el objeto es de ese tipo, use un molde normal.

Tenía una propiedad de Type para comparar y no podía usar is (como my_type is _BaseTypetoLookFor ), pero podría usar esto:

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

Observe que IsInstanceOfType y IsAssignableFrom devuelven true cuando se comparan los mismos tipos, donde IsSubClassOf devolverá false . E IsSubclassOf no funciona en las interfaces, donde los otros dos lo hacen. (Ver también esta pregunta y respuesta .)

 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 

Yo prefiero

Dicho eso, si lo estás utilizando, es probable que no estés usando la herencia correctamente.

Asum esa Persona: Entidad, y ese Animal: Entidad. Feed es un método virtual en Entity (para hacer feliz a 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 } } } 

Más bien

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

Si está usando C # 7, entonces es hora de actualizar la gran respuesta de Andrew Hare. La coincidencia de patrones ha introducido un buen atajo que nos da una variable tipada dentro del contexto de la statement if, sin requerir una statement / lanzamiento y verificación por separado:

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

Esto parece bastante decepcionante para un solo elenco como este, pero realmente brilla cuando tienes muchos tipos posibles entrando en tu rutina. La siguiente es la forma antigua de evitar lanzar dos veces:

 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 

Siempre me ha molestado trabajar para reducir este código tanto como sea posible, así como evitar duplicados de un mismo objeto. Lo anterior está muy bien comprimido con el patrón de coincidencia con lo siguiente:

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

EDITAR: Se actualizó el método nuevo más largo para usar un interruptor según el comentario de Palec.

Creo que el último también analiza la herencia (por ejemplo, Dog is Animal == true), que es mejor en la mayoría de los casos.

Depende de lo que estoy haciendo. Si necesito un valor de bool (por ejemplo, para determinar si voy a convertir a un int), usaré is . Si realmente necesito el tipo por algún motivo (por ejemplo, para pasar a otro método), GetType() .

El último es más limpio, más obvio, y también busca subtipos. Los otros no verifican el polymorphism.

Se usa para obtener el objeto System.Type para un tipo. Un tipo de expresión toma la siguiente 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 */ 

Esta muestra utiliza el método GetType para determinar el tipo que se utiliza para contener el resultado de un cálculo numérico. Esto depende de los requisitos de almacenamiento del número resultante.

  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; 

Puede usar el operador “typeof ()” en C # pero necesita llamar al espacio de nombres usando System.IO; Debe usar la palabra clave “es” si desea buscar un tipo.

Prueba de rendimiento 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(); } } 

Resultados en modo de depuración:

 00:00:08.4096636 00:00:10.8570657 

Resultados en modo de lanzamiento:

 00:00:02.3799048 00:00:07.1797128