Verificação de tipo: typeof, GetType ou is?

1513

Eu já vi muitas pessoas usarem o seguinte código:

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

Mas eu sei que você também pode fazer isso:

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

Ou isto:

if (obj1 is int)
    // Some code here

Pessoalmente, sinto que o último é o mais limpo, mas há algo que estou perdendo? Qual é o melhor para usar ou é preferência pessoal?

jasonh
fonte
28
Não esqueça as!
RCIX
82
asnão é realmente verificação de tipo embora ...
jasonh
49
asé certamente uma forma de verificação de tipo, tanto quanto isé! Ele efetivamente usa isnos bastidores e é usado em todo o MSDN em locais onde melhora a limpeza do código is. Em vez de isprocurar pela primeira vez, uma chamada para asestabelecer uma variável digitada pronta para uso: se for nula, responda adequadamente; caso contrário, continue. Certamente algo que eu já vi e usei bastante.
Zaccone
15
Há uma diferença significativa de desempenho em favor de as/ is(abordado em stackoverflow.com/a/27813381/477420 ) assumindo que seus trabalhos semânticos funcionam para o seu caso.
Alexei Levenkov
@ samusarin não "usa" a reflexão. O GetTypemétodo ao qual você está vinculando é System.Reflection.Assemblyum método completamente diferente e irrelevante aqui.
precisa

Respostas:

1849

Todos são diferentes.

  • typeof leva um nome de tipo (que você especifica em tempo de compilação).
  • GetType obtém o tipo de tempo de execução de uma instância.
  • is retorna true se uma instância estiver na árvore de herança.

Exemplo

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

Que tal typeof(T)? Também é resolvido em tempo de compilação?

Sim. T é sempre qual é o tipo da expressão. Lembre-se, um método genérico é basicamente um monte de métodos com o tipo apropriado. Exemplo:

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"
Jimmy
fonte
29
Ah, então, se eu tiver uma classe Ford derivada de Car e uma instância da Ford, marcar "is Car" nessa instância será verdadeiro. Faz sentido!
jasonh
2
Para esclarecer, eu sabia disso, mas comentei antes de adicionar um exemplo de código. Eu queria tentar adicionar um pouco de clareza em inglês à sua resposta já excelente.
jasonh
12
@Shimmy se typeof é avaliada em tempo de compilação e GetType () é avaliada em tempo de execução, então faz sentido que GetType () incorre em uma pequena queda de performance
Cedric Mamo
e o novo Dog (). GetType () é Animal Ou typeof (Dog) é Animal, apenas fornece um aviso e não um erro?
Prerak K
7
@PrerakK new Dog().GetType() is Animalretorna false (e sua outra versão também), pois .GetType()retorna um objeto do tipo Typee Typenão é um Animal.
Maarten
195

Use typeofquando quiser obter o tipo no momento da compilação . Use GetTypequando quiser obter o tipo no tempo de execução . Raramente existem casos a serem usados is, pois são convertidos e, na maioria dos casos, você acaba convertendo a variável de qualquer maneira.

Há uma quarta opção que você não considerou (especialmente se você também deseja converter um objeto para o tipo encontrado); isso é para usar as.

Foo foo = obj as Foo;

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

Isso usa apenas um elenco, enquanto esta abordagem:

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

requer dois .

Atualização (Jan 2020):

  • A partir do C # 7+ , agora você pode transmitir em linha, portanto a abordagem 'is' também pode ser feita em um elenco também.

Exemplo:

if(obj is Foo newLocalFoo)
{
    // For example, you can now reference 'newLocalFoo' in this local scope
    Console.WriteLine(newLocalFoo);
}
Andrew Hare
fonte
4
Com as alterações no .NET 4, isainda é possível executar uma conversão?
precisa saber é o seguinte
6
Esta resposta está correta? É verdade que você realmente pode passar uma instância para typeof ()? Minha experiência foi não. Mas acho que geralmente é verdade que a verificação de uma instância pode ter que acontecer em tempo de execução, enquanto a verificação de uma classe deve ser executável no momento da compilação.
Jon Coombs
4
@jon (4 anos após o seu q.), não, você não pode passar uma instância para typeof(), e esta resposta não sugere que você pode. Você passa o tipo, ou seja, typeof(string)funciona, typeof("foo")não.
Abel
Eu não acredito que isinterpreta elenco como tal, uma operação bastante especial em IL.
abatishchev
3
Agora podemos fazerif (obj is Foo foo) { /* use foo here */ }
Ivan García Topete
71

1

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

Isso é ilegal, porque typeoffunciona apenas em tipos, não em variáveis. Presumo que obj1 é uma variável. Portanto, dessa maneira, typeofé estático e funciona em tempo de compilação, em vez de em tempo de execução.

2)

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

Isto é, truese obj1é exatamente do tipo int. Se obj1deriva de int, a condição se será false.

3)

if (obj1 is int)

Isto é truese obj1é um int, ou se deriva de uma classe chamada int, ou se implementa uma interface chamada int.

Scott Langham
fonte
Pensando em 1, você está certo. E, no entanto, eu já vi isso em vários exemplos de código aqui. Deve ser o tipo t = obj1.GetType ();
jasonh
4
Sim, acho que sim. "typeof (obj1)" não compila quando eu tento.
Scott Langham
4
É impossível derivar de System.Int32 ou qualquer outro tipo de valor em C #
reggaeguitar
você pode dizer o que seria typeof (typeof (System.Int32))
Sana
1
@ Sana, por que você não tenta :) Imagino que você receba de volta uma instância de System.Type que representa o tipo System.Type! Documentação para typeof está aqui: docs.microsoft.com/en-us/dotnet/csharp/language-reference/...
Scott Langham
53
Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

Isto é um erro. O operador typeof em C # pode usar apenas nomes de tipos, não objetos.

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

Isso funcionará, mas talvez não como você esperaria. Para os tipos de valor, como você mostrou aqui, é aceitável, mas para os tipos de referência, ele só retornará true se o tipo for exatamente o mesmo tipo, e não outra coisa na hierarquia de herança. Por exemplo:

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

Isso imprimiria "o is something else", porque o tipo de oé Dog, não Animal. Você pode fazer isso funcionar, no entanto, se você usar o IsAssignableFrommétodo da Typeclasse.

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

Essa técnica ainda deixa um grande problema, no entanto. Se sua variável for nula, a chamada para GetType()lançará uma NullReferenceException. Então, para fazê-lo funcionar corretamente, você faria:

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

Com isso, você tem um comportamento equivalente à ispalavra - chave. Portanto, se esse é o comportamento que você deseja, use a ispalavra - chave, que é mais legível e mais eficiente.

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

Na maioria dos casos, porém, a ispalavra - chave ainda não é o que você realmente deseja, porque geralmente não é suficiente apenas saber que um objeto é de um determinado tipo. Geralmente, você deseja realmente usar esse objeto como uma instância desse tipo, o que exige a conversão também. E assim, você pode escrever código como este:

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

Mas isso faz com que o CLR verifique o tipo do objeto até duas vezes. Ele irá checá-lo uma vez para satisfazer o isoperador e, se ofor de fato um Animal, fazemos novamente o checagem para validar o elenco.

É mais eficiente fazer isso:

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

O asoperador é um elenco que não emitirá uma exceção se falhar, retornando null. Dessa forma, o CLR verifica o tipo de objeto apenas uma vez e, depois disso, precisamos fazer uma verificação nula, que é mais eficiente.

Mas cuidado: muitas pessoas caem em uma armadilha as. Como não gera exceções, algumas pessoas pensam nele como um elenco "seguro" e o usam exclusivamente, evitando os lançamentos regulares. Isso leva a erros como este:

(o as Animal).Speak();

Neste caso, o desenvolvedor está assumindo claramente que oirá sempre ser uma Animal, e desde que a sua hipótese é correta, tudo funciona bem. Mas se eles estão errados, então o que eles acabam com aqui é um NullReferenceException. Com um elenco regular, eles teriam conseguido um InvalidCastExceptionlugar, o que teria identificado mais corretamente o problema.

Às vezes, esse bug pode ser difícil de encontrar:

class Foo{
    readonly Animal animal;

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

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

Este é outro caso em que o desenvolvedor espera claramente oestar Animalsempre, mas isso não é óbvio no construtor, onde o aselenco é usado. Não é óbvio até que você chegue ao Interactmétodo, onde animalse espera que o campo seja atribuído positivamente. Nesse caso, você não apenas acaba com uma exceção enganosa, mas é lançada até potencialmente muito mais tarde do que quando ocorreu o erro real.

Em suma:

  • Se você só precisa saber se um objeto é ou não de algum tipo, use is.

  • Se você precisar tratar um objeto como uma instância de um determinado tipo, mas não tiver certeza de que o objeto será desse tipo, use ase verifique null.

  • Se você precisar tratar um objeto como uma instância de um determinado tipo, e o objeto for desse tipo, use uma conversão regular.

P Papai
fonte
o que há de errado com isso se (o é Animal) ((Animal) o) .Speak (); ? você pode por favor dar mais detalhes?
batmaci
2
@ batmaci: está na resposta - causa duas verificações de tipo. A primeira vez é o is Animal, o que exige que o CLR verifique se o tipo da variável oé um Animal. A segunda vez que verifica é quando lança na declaração ((Animal)o).Speak(). Em vez de verificar duas vezes, verifique uma vez usando as.
siride
Achei isso uma ótima explicação, obrigado por esclarecer!
Paul Efford 29/11/19
16

Se você estiver usando o C # 7, é hora de atualizar a ótima resposta de Andrew Hare. A correspondência de padrões introduziu um bom atalho que nos fornece uma variável digitada no contexto da instrução if, sem a necessidade de uma declaração / conversão e verificação separadas:

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

Isso parece bastante decepcionante para um elenco único como esse, mas realmente brilha quando você tem muitos tipos possíveis entrando em sua rotina. A seguir, é a maneira antiga de evitar transmitir duas vezes:

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

Trabalhar em torno da redução do código o máximo possível, além de evitar lançamentos duplicados do mesmo objeto, sempre me incomodou. O acima é bem compactado com o padrão correspondente ao seguinte:

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

EDIT: Atualizado o novo método mais longo para usar uma opção de acordo com o comentário do Palec.

JoelC
fonte
1
É recomendável usar a switchinstrução com correspondência de padrões neste caso.
Palec
Como você lidaria com um não é? Nesse bloco de código específico? if (obj1 is int integerValue) { integerValue++; }
Ben Vertonghen
Ben, se eu entendi sua pergunta, eu teria apenas uma declaração else para lidar com outros casos, já que você não pode colocar um número inteiro não em uma variável inteira. :)
JoelC 28/02/19
14

Eu tinha uma Typepropriedade -para comparar e não podia usar is(como my_type is _BaseTypetoLookFor), mas poderia usá-las:

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

Observe isso IsInstanceOfTypee IsAssignableFromretorne trueao comparar os mesmos tipos, onde IsSubClassOf retornará false. E IsSubclassOfnão funciona em interfaces, onde os outros dois fazem. (Veja também esta pergunta e resposta .)

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 Serious
fonte
9

Eu prefiro é

Dito isto, se você está usando é , você provavelmente está não usando herança corretamente.

Suponha que Pessoa: Entidade e esse Animal: Entidade. O feed é um método virtual na Entidade (para deixar Neil feliz)

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

Em vez

class Person
{
  public override void Feed( Person p )
  {
    // feed the person
  }
  public override void Feed( Animal a )
  {
    // feed the animal
  }
}
bobobobo
fonte
1
É verdade que eu nunca faria o primeiro, sabendo que Person deriva de Animal.
jasonh
3
O último também não está realmente usando herança. Foo deve ser um método virtual da Entidade que é substituído em Pessoa e Animal.
1111 Neil Williams
2
@obobobo Acho que você quer dizer "sobrecarga", não "herança".
lc.
@lc: Não, quero dizer herança. O primeiro exemplo é uma maneira incorreta (usar is ) de obter um comportamento diferente. O segundo exemplo usa sobrecarga yes, mas evita o uso de is .
bobobobo
1
O problema com o exemplo é que não seria escalável. Se você adicionou novas entidades que precisavam comer (por exemplo, um Inseto ou um Monstro), seria necessário adicionar um novo método na classe Entity e substituí-lo nas subclasses que o alimentariam. Isso não é mais preferível do que uma lista se (entidade é X) senão se (entidade é Y) ... Isso viola o LSP e o OCP, a herança provavelmente não é a melhor solução para o problema. Provavelmente seria preferida alguma forma de delegação.
ebrown
5

Acredito que o último também analise a herança (por exemplo, Dog is Animal == true), o que é melhor na maioria dos casos.

StriplingWarrior
fonte
2

Depende do que estou fazendo. Se eu precisar de um valor bool (por exemplo, para determinar se vou converter para um int), usarei is. Se eu realmente precisar do tipo por algum motivo (por exemplo, para passar para outro método), usarei GetType().

AllenG
fonte
1
Bom ponto. Esqueci de mencionar que cheguei a essa pergunta depois de analisar várias respostas que usavam uma instrução if para verificar um tipo.
jasonh
0

O último é mais limpo, mais óbvio e também verifica os subtipos. Os outros não verificam se há polimorfismo.

thecoop
fonte
0

Usado para obter o objeto System.Type para um tipo. Uma expressão typeof assume o seguinte formato:

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

Este exemplo usa o método GetType para determinar o tipo usado para conter o resultado de um cálculo numérico. Isso depende dos requisitos de armazenamento do 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
fonte
-4
if (c is UserControl) c.Enabled = enable;
Paulos02
fonte
4
Edite com mais informações. As respostas somente código e "tente isso" são desencorajadas, porque não contêm conteúdo pesquisável e não explicam por que alguém deveria "tentar fazer isso".
abarisone 6/09/16
Sua resposta não tem relação com a pergunta.
menxin 5/09/19
-5

Você pode usar o operador "typeof ()" em C #, mas precisa chamar o espaço para nome usando System.IO; Você deve usar a palavra-chave "is" se desejar verificar um tipo.

androidrill
fonte
7
typeofnão está definido em um espaço para nome, é uma palavra-chave. System.IOnão tem nada a ver com isso.
Arturo Torres Sánchez
-5

Teste de desempenho 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 no modo de depuração:

00:00:08.4096636
00:00:10.8570657

Resultados no modo de liberação:

00:00:02.3799048
00:00:07.1797128
Alexander Vasilyev
fonte
1
Não se deve usar DateTime.UtcNow para medidas de desempenho. Com seu código, mas com a classe Stopwatch, obtive resultados persistentemente opostos no modo Debug. UseTypeOf: 00: 00: 14.5074469 UseGetType: 00: 00: 10.5799534. Modo de liberação é o mesmo, como seria de esperar
Alexey Shcherbak
@AlexeyShcherbak A diferença entre Stopwatch e DateTime.Now não pode ser superior a 10-20 ms, verifique seu código novamente. E eu não me importo com milissegundos no meu teste. Além disso, meu código terá várias linhas de código com o cronômetro.
Alexander Vasilyev
1
é uma prática ruim em geral, não no seu caso particular.
Alexey Shcherbak
4
@AlexanderVasilyev A quantidade de linhas de código nunca deve ser usada como argumento para fazer algo documentadamente enganoso. Como visto em msdn.microsoft.com/en-us/library/system.datetime(v=vs.110).aspx , DateTimenão deve ser usado se você estiver preocupado com tempos abaixo de 100 ms , pois ele usa o período de tempo do sistema operacional. Comparativamente com Stopwatch, que usa os processadores Tick, a resolução usada por um DateTimeno Win7 é de 15ms.
Eric Wu