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



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

c#

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?




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.




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:

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

EDIT: aggiornato il nuovo metodo più lungo per utilizzare un interruttore come da commento di Palec.




Credo che l'ultimo guardi anche all'eredità (es. Dog is Animal == true), che è migliore nella maggior parte dei casi.




L'ultimo è più pulito, più ovvio e controlla anche i sottotipi. Gli altri non controllano il polimorfismo.




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



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





Related


Tags

c#