Frage Typprüfung: typeof, GetType oder is?


Ich habe viele Leute gesehen, die folgenden Code benutzen:

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

Aber ich weiß, du könntest das auch tun:

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

Oder dieses:

if (obj1 is int)
    // Some code here

Persönlich fühle ich, dass der letzte der sauberste ist, aber gibt es etwas, das mir fehlt? Welcher ist der beste, oder ist es persönliche Vorliebe?


1187
2018-06-11 19:10


Ursprung


Antworten:


Alle sind anders.

  • typeof Nimmt einen Typnamen (den Sie zum Zeitpunkt der Kompilierung angeben).
  • GetType Ruft den Laufzeittyp einer Instanz ab.
  • is gibt true zurück, wenn sich eine Instanz im Vererbungsbaum befindet.

Beispiel

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

Wie wäre es mit typeof(T)? Wird es auch zur Kompilierzeit aufgelöst?

Ja. T ist immer was der Typ des Ausdrucks ist. Denken Sie daran, dass eine generische Methode im Grunde eine ganze Reihe von Methoden mit dem entsprechenden Typ ist. Beispiel:

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



Benutzen typeof wenn du den Typ bekommen willst Kompilierungszeit. Benutzen GetType wenn du den Typ bekommen willst Ausführungszeit. Es gibt selten Fälle zu verwenden is wie es eine Besetzung tut, und in den meisten Fällen enden Sie sowieso, die Variable zu werfen.

Es gibt eine vierte Option, die Sie nicht berücksichtigt haben (insbesondere, wenn Sie ein Objekt auf den Typ anwenden, den Sie ebenfalls finden); das ist zu verwenden as.

Foo foo = obj as Foo;

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

Dies verwendet nur ein gegossen, während dieser Ansatz:

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

erfordert zwei.


163
2018-06-11 19:14



1.

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

Dies ist illegal, da typeof nur auf Typen und nicht auf Variablen funktioniert. Ich nehme an obj1 ist eine Variable. Auf diese Weise ist typeof statisch und arbeitet zur Kompilierungszeit statt zur Laufzeit.

2.

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

Dies ist der Fall, wenn obj1 genau vom Typ int ist. Wenn obj1 von int abgeleitet wird, ist die if-Bedingung falsch.

3.

if (obj1 is int)

Dies ist der Fall, wenn obj1 ein int ist oder wenn es von einer Klasse namens int abgeleitet ist oder wenn es eine Schnittstelle namens int implementiert.


59
2018-06-11 19:17



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

Dies ist ein Fehler. Der typeof-Operator in C # kann nur Typnamen und keine Objekte annehmen.

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

Das wird funktionieren, aber vielleicht nicht so, wie Sie es erwarten würden. Für Werttypen, wie Sie hier gezeigt haben, ist es akzeptabel, aber für Referenztypen würde es nur wahr zurückgeben, wenn der Typ der war genau dasselbe Typ, nicht etwas anderes in der Vererbungshierarchie. Zum Beispiel:

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

Dies würde drucken "o is something else", weil die Art von o ist Dognicht Animal. Sie können dies jedoch tun, wenn Sie das verwenden IsAssignableFrom Methode der Type Klasse.

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

Diese Technik hinterlässt jedoch immer noch ein großes Problem. Wenn Ihre Variable null ist, wird der Aufruf an GetType() wird eine NullReferenceException auslösen. Um es richtig zu machen, würden Sie tun:

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

Damit haben Sie ein gleichwertiges Verhalten des is Stichwort. Wenn dies das gewünschte Verhalten ist, sollten Sie daher das is Schlüsselwort, das lesbarer und effizienter ist.

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

In den meisten Fällen jedoch, die is Das Schlüsselwort ist immer noch nicht das, was Sie wirklich wollen, weil es normalerweise nicht ausreicht, nur zu wissen, dass ein Objekt von einem bestimmten Typ ist. Normalerweise willst du das eigentlich benutzen Dieses Objekt als eine Instanz dieses Typs, die es auch zu werfen erfordert. Und so können Sie vielleicht Code wie folgt schreiben:

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

Aber dadurch überprüft die CLR den Objekttyp zweimal. Es wird es einmal überprüfen, um das zu befriedigen is Betreiber und wenn o ist in der Tat ein AnimalWir lassen es erneut überprüfen, um den Cast zu validieren.

Es ist effizienter, dies stattdessen zu tun:

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

Das as Operator ist eine Besetzung, die keine Ausnahme auslöst, wenn sie fehlschlägt, und stattdessen zurückkehrt null. Auf diese Weise überprüft die CLR den Typ des Objekts nur einmal, und danach müssen wir nur noch eine Null-Überprüfung durchführen, was effizienter ist.

Aber Vorsicht: Viele Menschen fallen in eine Falle as. Da es keine Ausnahmen auslöst, denken manche Leute, dass es eine "sichere" Besetzung ist, und sie benutzen sie ausschließlich, indem sie regelmäßige Würfe meiden. Dies führt zu Fehlern wie dieser:

(o as Animal).Speak();

In diesem Fall geht der Entwickler eindeutig davon aus o werden immer Bohne Animalund solange ihre Annahme richtig ist, funktioniert alles gut. Aber wenn sie falsch liegen, dann enden sie mit einem NullReferenceException. Mit einer regulären Besetzung hätten sie eine bekommen InvalidCastException stattdessen, die das Problem richtiger identifiziert hätte.

Manchmal kann dieser Fehler schwer zu finden sein:

class Foo{
    readonly Animal animal;

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

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

Dies ist ein weiterer Fall, in dem der Entwickler eindeutig erwartet o ein ... sein Animal jedes Mal, aber das ist nicht offensichtlich im Konstruktor, wo die as Besetzung wird verwendet. Es ist nicht offensichtlich, bis Sie zum Interact Methode, wo die animal Es wird erwartet, dass das Feld positiv zugewiesen wird. In diesem Fall erhalten Sie nicht nur eine irreführende Ausnahme, sondern sie wird auch erst viel später ausgelöst, als der eigentliche Fehler aufgetreten ist.

Zusammenfassend:

  • Wenn Sie nur wissen müssen, ob ein Objekt von einem bestimmten Typ ist oder nicht, verwenden Sie is.

  • Wenn Sie ein Objekt als Instanz eines bestimmten Typs behandeln müssen, aber nicht sicher wissen, dass das Objekt von diesem Typ ist, verwenden Sie as und nachsehen null.

  • Wenn Sie ein Objekt als Instanz eines bestimmten Typs behandeln müssen und das Objekt von diesem Typ sein soll, verwenden Sie einen regulären Cast.


39
2018-06-11 19:34



Ich hatte ein Type-Eigenschaft zum Vergleichen und nicht verwenden is (mögen my_type is _BaseTypetoLookFor), aber ich könnte diese verwenden:

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

Beachte das IsInstanceOfType und IsAssignableFrom Rückkehr true Beim Vergleich der gleichen Typen wird IsSubClassOf zurückgegeben false. Und IsSubclassOf funktioniert nicht an Schnittstellen, wo die anderen beiden tun. (Siehe auch diese Frage und Antwort.)

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



ich bevorzuge ist

Das heißt, wenn Sie verwenden istDu bist wahrscheinlich nicht Vererbung richtig verwenden.

Angenommen, die Person: Entität und das Tier: Entität. Feed ist eine virtuelle Methode in Entity (um Neil glücklich zu machen)

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

Lieber

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



Wenn Sie C # 7 verwenden, ist es Zeit für ein Update auf Andrew Hares großartige Antwort. Mustererkennung hat eine nette Abkürzung eingeführt, die uns eine typisierte Variable im Kontext der if-Anweisung gibt, ohne dass eine separate Deklaration / Umwandlung und Überprüfung erforderlich ist:

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

Das sieht für einen einzelnen Cast einfach so überwältigend aus, aber es glänzt wirklich, wenn du viele mögliche Typen hast, die in deine Routine kommen. Das Folgende ist der alte Weg, um zweimal zu vermeiden:

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

Es hat mich schon immer gestört, dass ich daran gearbeitet habe, diesen Code so weit wie möglich zu verkleinern und doppelte Kopien desselben Objekts zu vermeiden. Das Obenstehende ist schön komprimiert mit folgendem Muster:

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

EDIT: Aktualisiert die längere neue Methode, um einen Schalter wie Palecs Kommentar zu verwenden.


6
2018-02-01 15:47



Ich glaube, dass der letzte auch Vererbung betrachtet (z.B. Hund ist Tier == wahr), was in den meisten Fällen besser ist.


5
2018-06-11 19:14



Es hängt davon ab, was ich mache. Wenn ich einen bool-Wert benötige (zum Beispiel um zu bestimmen, ob ich auf einen int-Wert gewandelt habe), werde ich verwenden is. Wenn ich den Typ tatsächlich aus irgendeinem Grund brauche (zB um zu einer anderen Methode überzugehen) werde ich verwenden GetType().


2
2018-06-11 19:14