Pregunta 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?


1187
2018-06-11 19:10


origen


Respuestas:


Todos son diferentes

  • typeof toma un nombre de tipo (que usted especifica 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é pasa typeof(T)? ¿También se resuelve en tiempo de compilació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>(T object) { 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"

1496
2018-06-11 19:15



Utilizar typeof cuando quieras obtener el tipo de tiempo de compilación. Utilizar GetType cuando quieras obtener el tipo de Tiempo de ejecución. Raramente hay casos para usar is como lo hace un yeso 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 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 uno lanzar mientras que este enfoque:

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

requiere dos.


163
2018-06-11 19:14



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 compilació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.


59
2018-06-11 19:17



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á verdadero si el tipo fue el 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 Dogno Animal. Puede hacer que esto funcione, sin embargo, si usa el IsAssignableFrom método de la Type clase.

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() lanzará 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, tienes un comportamiento equivalente al is palabra clave. Por lo tanto, si este es el comportamiento que desea, debe utilizar el is palabra clave, que es más legible y más eficiente.

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

En la mayoría de los casos, sin embargo, el is la palabra clave 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, quieres en realidad 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 del objeto hasta dos veces. Lo comprobará una vez para satisfacer el is operador, y si o es de hecho un Animal, hacemos que vuelva a verificar para validar el reparto.

Es más eficiente hacer esto en su lugar:

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

los as operador es un elenco que no lanzará una excepción si falla, en lugar de regresar 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 será siempre frijol Animaly, 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 reparto regular, habrían obtenido un InvalidCastException en cambio, lo 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 está esperando o ser un Animal cada vez, pero esto no es obvio en el constructor, donde as el yeso es usado. No es obvio hasta que llegas a la Interact método, donde animal Se espera que el campo sea asignado 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 verificar 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.


39
2018-06-11 19:34



Tuve un Type-propiedad para comparar y no poder usar is (me gusta my_type is _BaseTypetoLookFor), pero podría usar estos:

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

Darse cuenta de IsInstanceOfType y IsAssignableFrom regreso true al comparar los mismos tipos, donde IsSubClassOf regresará false. Y 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

11
2018-05-15 10:39



yo prefiero es

Dicho eso, si estás usando es, es probable no usando la herencia apropiadamente

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

7
2018-06-11 19:15



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 declaración if, sin requerir una declaración / 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.


6
2018-02-01 15:47