Comprobación de tipo: typeof, GetType o is?

1513

He visto a muchas personas usar el siguiente código:

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

Pero sé que también podrías hacer esto:

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

O esto:

if (obj1 is int)
    // Some code here

Personalmente, siento 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?

jasonh
fuente
28
No se olvide as!
RCIX
82
asno es realmente comprobación de tipos, aunque ...
JasonH
49
ases sin duda una forma de verificación de tipo, ¡tanto como ises! Se usa de manera efectiva isdetrás de escena y se usa en todo el lugar en MSDN en lugares donde mejora la limpieza del código versus is. En lugar de verificar isprimero, una llamada a asestablece una variable escrita que está lista para su uso: si es nula, responda adecuadamente; de lo contrario, proceda. Ciertamente, algo que he visto y usado bastante.
Zaccone
15
Hay una diferencia de rendimiento significativa a favor de as/ is(cubierto en stackoverflow.com/a/27813381/477420 ) suponiendo que funcione semánticamente para su caso.
Alexei Levenkov
@samusarin no "usa" la reflexión. El GetTypemétodo al que se está vinculando está en System.Reflection.Assemblyun método completamente diferente e irrelevante aquí.
Kirk Woll

Respuestas:

1849

Todos son diferentes

  • typeof toma un nombre de tipo (que especifique en tiempo de compilación).
  • GetType obtiene el tipo de tiempo de ejecución de una instancia.
  • is 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é hay de typeof(T)? ¿También se resuelve en tiempo de compilación?

Si. T siempre es 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>(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"
Palanqueta
fuente
29
Ah, entonces si tengo una clase de Ford que se deriva de Car y una instancia de Ford, marcar "is Car" en esa instancia será cierto. ¡Tiene sentido!
jasonh
2
Para aclarar, estaba al tanto de eso, pero comenté antes de agregar un código de muestra. Quería intentar agregar un poco de claridad en inglés a su ya excelente respuesta.
jasonh
12
@Shimmy si typeof se evalúa en tiempo de compilación y GetType () se evalúa en tiempo de ejecución, entonces tiene sentido que GetType () incurre en un ligero golpe de rendimiento
Cedric Mamo
¿Qué pasa con el nuevo Dog (). GetType () es Animal O typeof (Dog) es Animal, solo da una advertencia y no un error?
Prerak K
77
@PrerakK new Dog().GetType() is Animaldevuelve falso (y su otra versión también) ya que .GetType()devuelve un objeto de tipo Type, y Typeno es un Animal.
Maarten
195

Úselo typeofcuando desee obtener el tipo en el momento de la compilación . Úselo GetTypecuando desee obtener el tipo en el momento de la ejecución . Rara vez hay casos para usar, isya que hace un lanzamiento y, en la mayoría de los casos, terminas lanzando la variable de todos modos.

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

Foo foo = obj as Foo;

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

Esto solo usa un molde mientras que este enfoque:

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

requiere dos .

Actualización (enero de 2020):

  • A partir de C # 7+ , ahora puede lanzar en línea, por lo que el enfoque 'es' ahora también se puede hacer en un lanzamiento.

Ejemplo:

if(obj is Foo newLocalFoo)
{
    // For example, you can now reference 'newLocalFoo' in this local scope
    Console.WriteLine(newLocalFoo);
}
Andrew Hare
fuente
44
¿Con los cambios en .NET 4 istodavía se realiza un reparto?
ahsteele
66
¿Es correcta esta respuesta? ¿Es cierto que realmente puede pasar una instancia a typeof ()? Mi experiencia ha sido no. Pero supongo que en general es cierto que verificar una instancia podría tener lugar en tiempo de ejecución, mientras que verificar una clase debería ser factible en tiempo de compilación.
Jon Coombs
44
@jon (4 años después de su q.), no, no puede pasar una instancia typeof(), y esta respuesta no sugiere que pueda. En su lugar, pasa el tipo, es decir, typeof(string)funciona, typeof("foo")no.
Abel
No creo que isrealice el elenco como tal, sino una operación bastante especial en IL.
abatishchev
3
Ahora podemos hacerloif (obj is Foo foo) { /* use foo here */ }
Ivan García Topete
71

1)

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

Esto es ilegal, porque typeofsolo funciona en tipos, no en variables. Supongo que obj1 es una variable. Por lo tanto, de esta manera typeofes estático y funciona en tiempo de compilación en lugar de tiempo de ejecución.

2)

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

Esto es truesi obj1es exactamente de tipo int. Si obj1deriva de int, la condición if será false.

3)

if (obj1 is int)

Esto es truesi obj1es un int, o si deriva de una clase llamada int, o si implementa una interfaz llamada int.

Scott Langham
fuente
Pensando en 1, tienes razón. Y, sin embargo, lo he visto en varios ejemplos de código aquí. Debe ser Tipo t = obj1.GetType ();
jasonh
44
Sí, eso creo. "typeof (obj1)" no se compila cuando lo intento.
Scott Langham
44
Es imposible derivar de System.Int32 o cualquier otro tipo de valor en C #
reggaeguitar
¿Puedes decir qué sería typeof (typeof (system.int32))
Sana
1
@Sana, ¿por qué no lo intentas :) Me imagino que recuperas una instancia de System.Type que representa el tipo System.Type! La documentación para typeof está aquí: docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
Scott Langham
53
Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

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

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

Esto funcionará, pero quizás no como era de 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 otra cosa 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 se imprimiría "o is something else", porque el tipo de oes Dog, no Animal. Sin embargo, puede hacer que esto funcione si usa el IsAssignableFrommétodo de la Typeclase.

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 ispalabra clave. Por lo tanto, si este es el comportamiento que desea, debe usar la ispalabra clave, 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 ispalabra clave todavía no es lo que realmente desea, porque generalmente no es suficiente 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 lanzarlo. 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 de objeto hasta dos veces. Lo verificará una vez para satisfacer al isoperador, y si de ohecho es un Animal, lo haremos verificar nuevamente para validar el lanzamiento.

Es más eficiente hacer esto en su lugar:

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

El asoperador es un elenco que no lanzará una excepción si falla, sino que regresará null. De esta manera, el CLR verifica el tipo de objeto solo una vez, y después de eso, solo necesitamos hacer una verificación nula, que es más eficiente.

Pero cuidado: muchas personas caen en una trampa con as. Debido a que no arroja excepciones, algunas personas lo consideran un yeso "seguro", y lo usan exclusivamente, evitando los lanzamientos regulares. Esto lleva a errores como este:

(o as Animal).Speak();

En este caso, el desarrollador está asumiendo claramente que ohabrá siempre ser una Animal, y siempre que su suposición es correcta, todo funciona bien. Pero si están equivocados, entonces lo que terminan aquí es a NullReferenceException. Con un reparto regular, habrían obtenido un InvalidCastExceptionen su lugar, que habría identificado más correctamente el problema.

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 en el que el desarrollador claramente espera oserlo Animalcada vez, pero esto no es obvio en el constructor, donde asse usa el elenco. No es obvio hasta que llegue al Interactmétodo, donde animalse espera que el campo se asigne positivamente. En este caso, no solo termina con una excepción engañosa, sino que no se produce hasta potencialmente mucho más tarde que cuando ocurrió el error real.

En resumen:

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

  • Si necesita tratar un objeto como una instancia de cierto tipo, pero no está seguro de que el objeto sea de ese tipo, utilícelo asy verifique 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 regular.

Papi
fuente
qué hay de malo en esto si (o es Animal) ((Animal) o) .Speak (); ? ¿puedes por favor dar más detalles?
batmaci
2
@batmaci: está en la respuesta, provoca dos verificaciones de tipo. La primera vez es o is Animal, lo que requiere que el CLR verifique si el tipo de la variable oes an Animal. La segunda vez que verifica es cuando emite la declaración ((Animal)o).Speak(). En lugar de verificar dos veces, verifique una vez usando as.
siride
Me pareció una explicación absolutamente genial, ¡gracias por aclararme!
Paul Efford
16

Si está utilizando C # 7, entonces es hora de actualizar la excelente respuesta de Andrew Hare. La coincidencia de patrones ha introducido un buen acceso directo que nos da una variable tipeada dentro del contexto de la instrucción if, sin requerir una declaración / conversión 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

Trabajar para reducir este código tanto como sea posible, así como para evitar la duplicación de lanzamientos del mismo objeto siempre me ha molestado. Lo anterior está bien comprimido con un patrón que coincide 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 nuevo método más largo para usar un interruptor según el comentario de Palec.

JoelC
fuente
1
Utilizando switchEn este caso, es aconsejable declaración con coincidencia de patrones .
Palec
¿Cómo lidiarías con un no es? En este bloque de código en particular? if (obj1 is int integerValue) { integerValue++; }
Ben Vertonghen
Ben, si entiendo tu pregunta, solo tendría una instrucción else para manejar otros casos, ya que no puedes poner un entero no en una variable entera. :)
JoelC
14

Tenía una Typepropiedad para comparar y no podía usar is(me gusta my_type is _BaseTypetoLookFor), pero podía usar estos:

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

Tenga en cuenta que IsInstanceOfTypeyIsAssignableFrom regrese trueal comparar los mismos tipos, donde IsSubClassOf regresará false. Y IsSubclassOfno funciona en interfaces, donde los otros dos lo hacen. (Vea 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
Yahoo serio
fuente
9

yo prefiero es

Dicho esto, si estás usando es probable que no usando la herencia correctamente.

Asuma que 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
  }
}
bobobobo
fuente
1
Es cierto, nunca haría lo primero, sabiendo que la Persona deriva de Animal.
jasonh
3
Este último tampoco está usando la herencia. Foo debería ser un método virtual de Entidad que se anula en Persona y Animal.
Neil Williams
2
@bobobobo Creo que quieres decir "sobrecarga", no "herencia".
lc.
@lc: No, me refiero a la herencia. El primer ejemplo es un tipo de forma incorrecta (usando is ) para obtener un comportamiento diferente. El segundo ejemplo usa la sobrecarga yes, pero evita el uso de is .
bobobobo
1
El problema con el ejemplo es que no se escalará. Si agregó nuevas entidades que necesitaban comer (por ejemplo, un insecto o un monstruo), necesitaría agregar un nuevo método en la clase Entidad y luego anularlo en las subclases que lo alimentarían. Esto no es más preferible que una lista si (entidad es X) más si (entidad es Y) ... Esto viola el LSP y el OCP, la herencia probablemente no sea la mejor solución al problema. Probablemente se preferiría alguna forma de delegación.
ebrown
5

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.

StriplingWarrior
fuente
2

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

AllenG
fuente
1
Buen punto. Olvidé mencionar que llegué a esta pregunta después de mirar varias respuestas que usaban una declaración if para verificar un tipo.
jasonh
0

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

thecoop
fuente
0

Se usa para obtener el objeto System.Type para un tipo. Una expresión typeof 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
    */
Muhammad Awais
fuente
-4
if (c is UserControl) c.Enabled = enable;
Paulos02
fuente
44
Edite con más información. Se desaconsejan las respuestas de solo código y "pruebe esto", ya que no contienen contenido que se pueda buscar y no explican por qué alguien debería "probar esto".
abarisone
Su respuesta no está relacionada con la pregunta.
menxin
-5

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

androidrill
fuente
77
typeofno está definido en un espacio de nombres, es una palabra clave. System.IONo tiene nada que ver con esto.
Arturo Torres Sánchez
-5

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>(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 liberación:

00:00:02.3799048
00:00:07.1797128
Alexander Vasilyev
fuente
1
No se debe usar DateTime.UtcNow para las medidas de rendimiento. Con su código, pero con la clase Cronómetro, obtuve resultados constantemente opuestos para el modo de depuración. UseTypeOf: 00: 00: 14.5074469 UseGetType: 00: 00: 10.5799534. El modo de lanzamiento es el mismo, como se esperaba
Alexey Shcherbak
@AlexeyShcherbak La diferencia entre cronómetro y fecha y hora. Ahora no puede ser más de 10-20 ms, verifique su código nuevamente. Y no me importan los milisegundos en mi prueba. Además, mi código tendrá varias líneas de código más largas con Stopwatch.
Alexander Vasilyev
1
Es una mala práctica en general, no en su caso particular.
Alexey Shcherbak
44
@AlexanderVasilyev La cantidad de líneas de código nunca debe usarse como argumento para hacer algo documentadamente engañoso. Como se ve en msdn.microsoft.com/en-us/library/system.datetime(v=vs.110).aspx , DateTimeno debe usarse si le preocupan los tiempos por debajo de 100 ms , ya que usa el marco de tiempo del sistema operativo. En comparación con Stopwatch, que utiliza los procesadores Tick, la resolución utilizada por a DateTimeen Win7 es de 15 ms.
Eric Wu