1857 Stimmen

Typprüfung: typeof, GetType, oder is?

Ich habe gesehen, dass viele Leute den folgenden Code verwenden:

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

Aber ich weiß, dass Sie das auch tun können:

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

Oder dies:

if (obj1 is int)
    // Some code here

Ich persönlich finde die letzte Variante am saubersten, aber gibt es etwas, das ich übersehe? Welches ist am besten zu verwenden, oder ist das eine persönliche Vorliebe?

2213voto

Jimmy Punkte 85199

Alle sind unterschiedlich.

  • typeof nimmt einen Typnamen (den Sie zur Kompilierzeit angeben).
  • GetType ermittelt den Laufzeittyp einer Instanz.
  • is gibt true zurück, wenn sich eine Instanz im Vererbungsbaum befindet.

Exemple

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

Was ist mit typeof(T) ? Wird es auch zur Kompilierzeit gelöst?

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

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"

251voto

Andrew Hare Punkte 332190

Utilice typeof wenn Sie den Typ bei Kompilierungszeit . Verwenden Sie GetType wenn Sie den Typ bei Ausführungszeit . Es gibt selten Fälle, in denen die is da es sich um einen Cast handelt und man in den meisten Fällen die Variable sowieso castet.

Es gibt noch eine vierte Möglichkeit, die Sie nicht in Betracht gezogen haben (vor allem, wenn Sie ein Objekt in den gefundenen Typ umwandeln wollen); das ist die Verwendung von as .

Foo foo = obj as Foo;

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

Dies verwendet nur eine gießen in der Erwägung, dass dieser Ansatz:

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

erfordert zwei .

Aktualisierung (Januar 2020):

  • Ab C# 7+ können Sie jetzt inline casten, so dass der "is"-Ansatz jetzt auch in einem Cast durchgeführt werden kann.

Ejemplo:

if(obj is Foo newLocalFoo)
{
    // For example, you can now reference 'newLocalFoo' in this local scope
    Console.WriteLine(newLocalFoo);
}

105voto

Scott Langham Punkte 55597

1.

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

Dies ist illegal, weil typeof funktioniert nur bei Typen, nicht bei Variablen. Ich nehme an, obj1 ist eine Variable. Also, auf diese Weise typeof ist statisch und erledigt seine Arbeit zur Kompilierzeit und nicht zur Laufzeit.

2.

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

Dies ist true wenn obj1 ist genau vom Typ int . Wenn obj1 leitet sich ab von int wird die if-Bedingung wie folgt lauten false .

3.

if (obj1 is int)

Dies ist true wenn obj1 ist eine int oder wenn sie von einer Klasse namens int oder wenn es eine Schnittstelle namens int .

61voto

P Daddy Punkte 27687
Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

Hier liegt ein Fehler vor. Der typeof-Operator in C# kann nur Typnamen annehmen, keine Objekte.

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 true zurückgeben, wenn der Typ der 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 Folgendes drucken "o is something else" weil die Art der o es Dog , nicht Animal . Sie können dies jedoch bewerkstelligen, wenn Sie die IsAssignableFrom Methode der Type Klasse.

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

Diese Technik birgt jedoch noch ein großes Problem. Wenn Ihre Variable null ist, wird der Aufruf von GetType() wird eine NullReferenceException auslösen. Damit es richtig funktioniert, müssen Sie also Folgendes tun:

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

Damit haben Sie ein gleichwertiges Verhalten der is Schlüsselwort. Wenn Sie dieses Verhalten wünschen, sollten Sie daher das is Schlüsselwort, das besser lesbar und effizienter ist.

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

In den meisten Fällen ist jedoch die is Schlüsselwort ist immer noch nicht das, was Sie wirklich wollen, denn es reicht normalerweise nicht aus, nur zu wissen, dass ein Objekt von einem bestimmten Typ ist. Normalerweise wollen Sie tatsächlich verwenden. dieses Objekt als eine Instanz dieses Typs, was ebenfalls ein Casting erfordert. Und so kann es passieren, dass Sie Code wie diesen schreiben:

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

Dies führt jedoch dazu, dass die CLR den Typ des Objekts bis zu zwei Mal überprüft. Sie wird ihn einmal prüfen, um die is Operator, und wenn o ist in der Tat ein Animal lassen wir es noch einmal überprüfen, um die Besetzung zu bestätigen.

Es ist effizienter, dies stattdessen zu tun:

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

El as Operator ist ein Cast, der keine Ausnahme auslöst, wenn er fehlschlägt, sondern stattdessen eine null . Auf diese Weise überprüft die CLR den Typ des Objekts nur einmal, und danach müssen wir nur noch eine Nullprüfung durchführen, was effizienter ist.

Aber Vorsicht: Viele Menschen tappen in eine Falle mit as . Da er keine Ausnahmen auslöst, halten ihn manche Leute für einen "sicheren" Cast und verwenden ihn ausschließlich, während sie normale Casts meiden. Dies führt zu Fehlern wie diesem:

(o as Animal).Speak();

In diesem Fall geht der Entwickler eindeutig davon aus, dass o wird immer ein sein Animal und solange ihre Annahme richtig ist, funktioniert alles gut. Aber wenn sie sich irren, dann ist das, was sie hier am Ende haben, eine NullReferenceException . Mit einer normalen Besetzung hätten sie eine InvalidCastException was das Problem besser erkannt 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, dass o zu sein Animal jedes Mal, aber das ist nicht offensichtlich im Konstruktor, wo die as Guss verwendet wird. Es ist nicht offensichtlich, bis man zu dem Interact Methode, bei der die animal Es wird erwartet, dass das Feld positiv belegt wird. In diesem Fall kommt es nicht nur zu einer irreführenden Ausnahme, sondern sie wird auch erst viel später ausgelöst, als der eigentliche Fehler aufgetreten ist.

Zusammengefasst:

  • 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 sein wird, verwenden Sie as und prüfen auf null .

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

22voto

JoelC Punkte 3332

Wenn Sie C# 7 verwenden, dann ist es Zeit für ein Update der großartigen Antwort von Andrew Hare. Musterabgleich hat eine nette Abkürzung eingeführt, die uns eine typisierte Variable im Kontext der if-Anweisung liefert, ohne dass eine separate Deklaration/Cast und Prüfung erforderlich ist:

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

Das sieht für einen einzelnen Wurf wie diesen ziemlich unscheinbar aus, glänzt aber wirklich, wenn du viele mögliche Typen in deine Routine einfließen lässt. Die unten ist der alte Weg, um zu vermeiden, zweimal zu werfen:

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, diesen Code so weit wie möglich zu komprimieren und doppelte Besetzungen desselben Objekts zu vermeiden. Die oben genannten ist schön komprimiert mit Musterabgleich zu den folgenden:

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

EDIT: Die längere neue Methode wurde aktualisiert, um einen Schalter gemäß Palec's Kommentar zu verwenden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X