Sprawdzanie typu: typeof, GetType, czy jest?

1512

Widziałem wiele osób używających następującego kodu:

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

Ale wiem, że możesz to zrobić:

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

Albo to:

if (obj1 is int)
    // Some code here

Osobiście uważam, że ostatni jest najczystszy, ale czy czegoś mi brakuje? Który jest najlepszy w użyciu, czy jest to osobiste preferencje?

Jason
źródło
27
Nie zapomnij as!
RCIX
82
asnie jest tak naprawdę sprawdzaniem typów ...
jasonh
49
asjest z pewnością formą sprawdzania typu, tak samo jak isjest! Skutecznie wykorzystuje isza kulisami i jest używany wszędzie w MSDN w miejscach, w których poprawia czystość kodu w porównaniu do is. Zamiast sprawdzania ispierwszego, wywołanie asustanawia zmienną tekstową, która jest gotowa do użycia: Jeśli jest pusta, odpowiedz odpowiednio; w przeciwnym razie kontynuuj. Z pewnością coś, co widziałem i używałem całkiem sporo.
Zaccone,
15
Istnieje znacząca różnica w wydajności na korzyść as/ is(omówionego na stackoverflow.com/a/27813381/477420 ), zakładając, że jego semantyczna praca dla twojego przypadku.
Aleksiej Lewenkow
@samusarin nie używa „refleksji”. GetTypeSposób łączysz się jest System.Reflection.Assembly- zupełnie inny sposób i bez znaczenia tutaj.
Kirk Woll,

Odpowiedzi:

1847

Wszystkie są różne.

  • typeof przyjmuje nazwę typu (którą określasz podczas kompilacji).
  • GetType pobiera typ środowiska wykonawczego instancji.
  • is zwraca true, jeśli instancja znajduje się w drzewie dziedziczenia.

Przykład

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

Co typeof(T)? Czy jest to również rozwiązywane w czasie kompilacji?

Tak. T jest zawsze rodzajem wyrażenia. Pamiętaj, że metoda ogólna to w zasadzie cała masa metod odpowiedniego typu. Przykład:

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
źródło
29
Ach, więc jeśli mam klasę Forda wywodzącą się z samochodu i instancji Forda, sprawdzenie „jest samochodem” w tym przypadku będzie prawdziwe. Ma sens!
jasonh
2
Aby to wyjaśnić, wiedziałem o tym, ale skomentowałem, zanim dodałeś próbkę kodu. Chciałem spróbować dodać jasnej angielskiej jasności do i tak już doskonałej odpowiedzi.
jasonh
12
@Shimmy, jeśli typof jest oceniany w czasie kompilacji, a GetType () jest oceniany w czasie wykonywania, to ma sens, że GetType () otrzymuje niewielki hit wydajności
Cedric Mamo
co z nowym Dog (). GetType () to Animal Lub typeof (Dog) to Animal, to tylko ostrzeżenie, a nie błąd?
Prerak K
7
@ PrerakK new Dog().GetType() is Animalzwraca wartość false (a także twoją inną wersję), ponieważ .GetType()zwraca obiekt typu Typei Typenie jest Animal.
Maarten
195

Użyj, typeofjeśli chcesz uzyskać typ w czasie kompilacji . Użyj, GetTypejeśli chcesz uzyskać typ w czasie wykonywania . Rzadko są jakieś przypadki do użycia, isponieważ wykonuje rzutowanie, aw większości przypadków i tak rzucasz zmienną.

Istnieje czwarta opcja, której nie wziąłeś pod uwagę (szczególnie jeśli zamierzasz rzucić obiekt również na typ, który znajdziesz); to jest do użycia as.

Foo foo = obj as Foo;

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

To używa tylko jednej obsady, podczas gdy takie podejście:

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

wymaga dwóch .

Aktualizacja (styczeń 2020):

  • Od wersji C # 7+ można teraz rzucać w linii, więc podejście „jest” można teraz wykonać także w jednym rzucie.

Przykład:

if(obj is Foo newLocalFoo)
{
    // For example, you can now reference 'newLocalFoo' in this local scope
    Console.WriteLine(newLocalFoo);
}
Andrew Hare
źródło
4
Ze zmianami w .NET 4 isnadal wykonuje rzutowanie?
ahsteele
6
Czy ta odpowiedź jest poprawna? Czy to prawda, że ​​naprawdę można przekazać instancję do typeof ()? Moje doświadczenie jest nie. Ale wydaje mi się, że ogólnie jest prawdą, że sprawdzanie instancji może się zdarzyć w czasie wykonywania, podczas gdy sprawdzanie klasy powinno być wykonalne w czasie kompilacji.
Jon Coombs
4
@jon (4 lata po twoim q.), nie, nie możesz przekazać instancji typeof(), a ta odpowiedź nie sugeruje, że możesz. Zamiast tego podajesz typ, tzn. typeof(string)Działa, typeof("foo")nie.
Abel
Nie wierzę, że iswykonuje się obsada jako taka, raczej specjalna operacja w IL.
abatishchev
3
Możemy teraz zrobićif (obj is Foo foo) { /* use foo here */ }
Ivan García Topete,
71

1.

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

Jest to nielegalne, ponieważ typeofdziała tylko na typach, a nie na zmiennych. Zakładam, że obj1 jest zmienną. W ten sposób typeofjest statyczny i działa w czasie kompilacji zamiast w środowisku wykonawczym.

2)

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

To jest, truejeśli obj1jest dokładnie typu int. Jeśli obj1pochodzi od int, warunek if będzie false.

3)

if (obj1 is int)

Dzieje się tak, truejeśli obj1jest int, lub jeśli pochodzi od klasy o nazwie int, lub jeśli implementuje interfejs o nazwie int.

Scott Langham
źródło
Myśląc o 1, masz rację. A jednak widziałem to tutaj w kilku przykładach kodu. Powinien to być typ t = obj1.GetType ();
jasonh
4
Tak myślę. „typeof (obj1)” nie kompiluje się, gdy próbuję.
Scott Langham,
4
Nie można wyprowadzić z System.Int32 lub jakiegokolwiek innego typu wartości w C #
reggaeguitar
czy możesz powiedzieć, co byłoby typof (typeof (system.int32))
Sana
1
@Sana, dlaczego nie spróbujesz :) Wyobrażam sobie, że odzyskasz instancję System.Type reprezentującą typ System.Type! Dokumentacja dla typeof znajduje się tutaj: docs.microsoft.com/en-us/dotnet/csharp/language-reference/...
Scott Langham
53
Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

To jest błąd. Operator typeof w języku C # może przyjmować tylko nazwy typów, a nie obiekty.

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

To zadziała, ale może nie tak, jak można się spodziewać. W przypadku typów wartości, jak pokazano tutaj, jest to akceptowalne, ale dla typów referencyjnych zwróciłoby wartość true tylko wtedy, gdy typ był dokładnie tego samego typu, a nie coś innego w hierarchii dziedziczenia. Na przykład:

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

Byłoby to wydrukować "o is something else", ponieważ typ ojest Dog, nie Animal. Możesz jednak sprawić, aby to zadziałało, jeśli użyjesz IsAssignableFrommetody Typeklasy.

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

Ta technika wciąż jednak stanowi poważny problem. Jeśli twoja zmienna ma wartość NULL, wywołanie GetType()to wyrzuci NullReferenceException. Aby więc działało poprawnie, wykonaj następujące czynności:

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

Dzięki temu masz równoważne zachowanie issłowa kluczowego. Dlatego jeśli takie zachowanie jest pożądane, należy użyć issłowa kluczowego, które jest bardziej czytelne i wydajniejsze.

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

Jednak w większości przypadków issłowo kluczowe nadal nie jest tym, czego naprawdę chcesz, ponieważ zwykle nie wystarczy po prostu wiedzieć, że obiekt jest określonego typu. Zwykle chcesz faktycznie użyć tego obiektu jako instancji tego typu, co również wymaga jego rzutowania. I może się okazać, że piszesz taki kod:

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

Ale to powoduje, że CLR sprawdza typ obiektu maksymalnie dwa razy. Sprawdzi to raz, aby zadowolić isoperatora, a jeśli otak Animal, to sprawdzimy ponownie, aby zweryfikować rzut.

Zamiast tego bardziej efektywnie jest to zrobić:

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

asOperator jest obsada, że nie rzuci wyjątek, jeśli to się nie powiedzie, zamiast wracać null. W ten sposób CLR sprawdza typ obiektu tylko raz, a następnie musimy po prostu sprawdzić zero, co jest bardziej wydajne.

Ale uwaga: wiele osób wpada w pułapkę as. Ponieważ nie rzuca wyjątków, niektórzy uważają go za „bezpieczną” obsadę i używają jej wyłącznie, unikając regularnych rzutów. Prowadzi to do takich błędów:

(o as Animal).Speak();

W tym przypadku, deweloper jest wyraźnie zakładając, że obędzie zawsze być Animal, i tak długo, jak ich założenie jest słuszne, wszystko działa bez zarzutu. Ale jeśli się mylą, to kończą się tutaj NullReferenceException. W przypadku zwykłej obsady otrzymaliby InvalidCastExceptionzamiast tego, co bardziej poprawnie zidentyfikowałoby problem.

Czasami ten błąd może być trudny do znalezienia:

class Foo{
    readonly Animal animal;

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

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

Jest to kolejny przypadek, w którym deweloper wyraźnie spodziewa osię za Animalkażdym razem, ale nie jest to oczywiste w konstruktorze, w którym asużywana jest obsada. Nie jest to oczywiste, dopóki nie przejdziesz do Interactmetody, w której animalpole ma zostać pozytywnie przypisane. W tym przypadku nie tylko trafiasz na wprowadzający w błąd wyjątek, ale jest on zgłaszany dopiero potencjalnie znacznie później niż wtedy, gdy wystąpił rzeczywisty błąd.

W podsumowaniu:

  • Jeśli potrzebujesz tylko wiedzieć, czy obiekt jest jakiegoś typu, użyj is.

  • Jeśli musisz traktować obiekt jako instancję określonego typu, ale nie wiesz na pewno, że obiekt będzie tego typu, użyj asi sprawdź null.

  • Jeśli chcesz traktować obiekt jako instancję określonego typu, a obiekt ma być tego typu, użyj zwykłego rzutowania.

P Tatusiu
źródło
co jest z tym nie tak, jeśli (o to Animal) ((Animal) o) .Speak (); ? czy możesz podać więcej szczegółów?
batmaci
2
@batmaci: jest w odpowiedzi - powoduje dwa sprawdzenia typu. Za pierwszym razem o is Animalwymaga CLR, aby sprawdzić, czy typ zmiennej oto Animal. Po raz drugi sprawdza, kiedy rzuca oświadczenie ((Animal)o).Speak(). Zamiast sprawdzać dwa razy, sprawdź raz za pomocą as.
siride
Uważam to za absolutnie świetne wytłumaczenie, dziękuję za wyjaśnienie!
Paul Efford
16

Jeśli używasz C # 7, nadszedł czas na aktualizację świetnej odpowiedzi Andrew Hare. Dopasowanie wzorca wprowadziło ładny skrót, który daje nam zmienną tekstową w kontekście instrukcji if, bez konieczności oddzielnej deklaracji / rzutowania i sprawdzenia:

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

Wygląda to dość rozczarowująco dla jednej takiej obsady, ale naprawdę świeci, gdy masz wiele możliwych typów wchodzących w twoją rutynę. Poniżej znajduje się stary sposób na uniknięcie dwukrotnego rzucenia:

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

Praca nad zmniejszaniem tego kodu w jak największym stopniu, a także unikaniem duplikatów rzutów tego samego obiektu zawsze mnie niepokoiła. Powyższe jest ładnie skompresowane z dopasowaniem wzorca do następujących:

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

EDYCJA: Zaktualizowano dłuższą nową metodę używania przełącznika zgodnie z komentarzem Palec.

JoelC
źródło
1
W takim przypadku wskazane jest użycie switchinstrukcji z dopasowaniem wzorca .
Palec
Jak sobie poradzisz z nie jest? W tym konkretnym bloku kodu? if (obj1 is int integerValue) { integerValue++; }
Ben Vertonghen,
Ben, jeśli rozumiem twoje pytanie, po prostu miałbym instrukcję else do obsługi innych przypadków, ponieważ nie możesz wstawić liczby całkowitej nie do zmiennej całkowitej. :)
JoelC
14

Miałem Typewłaściwość do porównania i nie mogłem używać is(jak my_type is _BaseTypetoLookFor), ale mogłem użyć tych:

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

Zauważ to IsInstanceOfTypei IsAssignableFromzwróć true, porównując te same typy, do których zwróci IsSubClassOf false. I IsSubclassOfnie działa na interfejsach, na których działają pozostałe dwa. (Zobacz także to pytanie i odpowiedź ).

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
Poważnie
źródło
9

Wolę to

Mimo to, jeśli używasz jest , jesteś prawdopodobnie nie prawidłowo za pomocą dziedziczenia.

Załóżmy, że osoba: istota i to zwierzę: istota. Kanał jest wirtualną metodą w Entity (aby uszczęśliwić Neila)

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

Raczej

class Person
{
  public override void Feed( Person p )
  {
    // feed the person
  }
  public override void Feed( Animal a )
  {
    // feed the animal
  }
}
Bobobobo
źródło
1
To prawda, że ​​nigdy nie zrobiłbym tego pierwszego, wiedząc, że Osoba wywodzi się od Zwierząt.
jasonh
3
Ten ostatni też tak naprawdę nie korzysta z dziedziczenia. Foo powinna być wirtualną metodą bytu, która jest nadpisana w Person and Animal.
Neil Williams,
2
@ bobobobo Myślę, że masz na myśli „przeładowanie”, a nie „dziedziczenie”.
lc.
@ lc: Nie, mam na myśli dziedziczenie. Pierwszy przykład to rodzaj niewłaściwego sposobu (użycie is ) uzyskania różnych zachowań. Drugim przykładem zastosowania przeciążenia tak, ale unika stosowania jest .
bobobobo
1
Problem z przykładem polega na tym, że nie byłby skalowany. Jeśli dodałeś nowe byty, które musiały jeść (na przykład Owad lub Potwór), musiałbyś dodać nową metodę do klasy Entity, a następnie zastąpić ją w podklasach, które by ją karmiły. Nie jest to bardziej preferowane niż lista, jeśli (jednostka to X), jeśli (jednostka to Y) ... To narusza LSP i OCP, dziedziczenie prawdopodobnie nie jest najlepszym rozwiązaniem problemu. Pewna forma przekazania prawdopodobnie byłaby preferowana.
ebrown
5

Uważam, że ostatni dotyczy również dziedziczenia (np. Pies to zwierzę == prawda), co w większości przypadków jest lepsze.

StriplingWarrior
źródło
2

To zależy od tego, co robię. Jeśli potrzebuję wartości bool (powiedzmy, aby ustalić, czy rzuciłem na int), użyję is. Jeśli rzeczywiście potrzebuję tego typu z jakiegoś powodu (powiedzmy, aby przejść do innej metody) użyję GetType().

AllenG
źródło
1
Słuszna uwaga. Zapomniałem wspomnieć, że doszedłem do tego pytania po zapoznaniu się z kilkoma odpowiedziami, w których do sprawdzenia typu użyłem instrukcji if.
jasonh
0

Ostatni jest czystszy, bardziej oczywisty, a także sprawdza podtypy. Inni nie sprawdzają polimorfizmu.

Thecoop
źródło
0

Służy do uzyskania obiektu System.Type dla typu. Wyrażenie typeof przybiera następującą postać:

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

W tym przykładzie użyto metody GetType, aby określić typ, który jest używany do przechowywania wyniku obliczeń numerycznych. Zależy to od wymagań dotyczących przechowywania wynikowej liczby.

    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
źródło
-4
if (c is UserControl) c.Enabled = enable;
Paulos02
źródło
4
Edytuj, podając więcej informacji. Odradzane są tylko kody i odpowiedzi „wypróbuj to”, ponieważ nie zawierają one treści, które można przeszukiwać, i nie wyjaśniają, dlaczego ktoś powinien „wypróbować”.
abarisone
Twoja odpowiedź nie ma związku z pytaniem.
menxin
-5

Możesz użyć operatora „typeof ()” w języku C #, ale musisz wywołać przestrzeń nazw przy użyciu System.IO; Musisz użyć słowa kluczowego „is”, jeśli chcesz sprawdzić typ.

androidrill
źródło
7
typeofnie jest zdefiniowany w przestrzeni nazw, jest to słowo kluczowe. System.IOnie ma z tym nic wspólnego.
Arturo Torres Sánchez
-5

Test wydajności 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();
    }
}

Wyniki w trybie debugowania:

00:00:08.4096636
00:00:10.8570657

Wyniki w trybie zwolnienia:

00:00:02.3799048
00:00:07.1797128
Aleksander Wasiljew
źródło
1
Nie należy używać DateTime.UtcNow do pomiaru wydajności. Z twoim kodem, ale z klasą Stopwatch, ciągle mam przeciwne wyniki dla trybu Debugowania. UseTypeOf: 00: 00: 14.5074469 UseGetType: 00: 00: 10.5799534. Tryb zwolnienia jest taki sam, jak oczekiwano
Alexey Shcherbak,
@AlexeyShcherbak Różnica między Stoperem i DateTime.Now nie może być większa niż 10-20 ms, sprawdź kod ponownie. W moim teście nie dbam o milisekundy. Również mój kod będzie zawierał kilka linii kodu dłużej ze Stopwatch.
Aleksander Wasiljew
1
ogólnie jest to zła praktyka, nie w twoim przypadku.
Alexey Shcherbak
4
@AlexanderVasilyev Ilość wierszy kodu nigdy nie powinna być używana jako argument do robienia czegoś, co zostało udokumentowane jako wprowadzające w błąd. Jak widać w msdn.microsoft.com/en-us/library/system.datetime(v=vs.110).aspx , DateTimenie należy go używać, jeśli obawiasz się czasów poniżej 100 ms , ponieważ wykorzystuje on ramy czasowe systemu operacyjnego. Porównując z Stopwatch, która wykorzystuje procesory Tick, rozdzielczość używana przez a DateTimew Win7 to aż 15 ms.
Eric Wu