[c#] Tipo di controllo: typeof, GetType o is?


Answers

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 oggetto 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 .

Question

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?




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ù possibile questo codice, oltre a evitare duplicati dello stesso oggetto mi ha sempre infastidito. Quanto sopra è ben compresso con pattern matching al seguente:

if (obj1 is Button button)
{
    // do stuff...
}
else if (obj1 is TextBox text)
{
    // do stuff...
}
else if (obj1 is Label label)
{
    // do stuff...
}
// ... and so on



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



if (c is UserControl) c.Enabled = enable;



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 classe 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 oggetto è di un certo tipo. Di solito, vuoi effettivamente usare quell'oggetto 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'oggetto 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'oggetto 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 oggetto è o meno di qualche tipo, l'uso is .

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

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




Utilizzato per ottenere l'oggetto 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
    */



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



Related



Tags

c# c#