Type Checking: typeof, GetType, or is?

前端 未结 14 2274
北海茫月
北海茫月 2020-11-22 00:49

I\'ve seen many people use the following code:

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

But I know you could also

14条回答
  •  既然无缘
    2020-11-22 01:21

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

    This is an error. The typeof operator in C# can only take type names, not objects.

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

    This will work, but maybe not as you would expect. For value types, as you've shown here, it's acceptable, but for reference types, it would only return true if the type was the exact same type, not something else in the inheritance hierarchy. For instance:

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

    This would print "o is something else", because the type of o is Dog, not Animal. You can make this work, however, if you use the IsAssignableFrom method of the Type class.

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

    This technique still leaves a major problem, though. If your variable is null, the call to GetType() will throw a NullReferenceException. So to make it work correctly, you'd do:

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

    With this, you have equivalent behavior of the is keyword. Hence, if this is the behavior you want, you should use the is keyword, which is more readable and more efficient.

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

    In most cases, though, the is keyword still isn't what you really want, because it's usually not enough just to know that an object is of a certain type. Usually, you want to actually use that object as an instance of that type, which requires casting it too. And so you may find yourself writing code like this:

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

    But that makes the CLR check the object's type up to two times. It will check it once to satisfy the is operator, and if o is indeed an Animal, we make it check again to validate the cast.

    It's more efficient to do this instead:

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

    The as operator is a cast that won't throw an exception if it fails, instead returning null. This way, the CLR checks the object's type just once, and after that, we just need to do a null check, which is more efficient.

    But beware: many people fall into a trap with as. Because it doesn't throw exceptions, some people think of it as a "safe" cast, and they use it exclusively, shunning regular casts. This leads to errors like this:

    (o as Animal).Speak();
    

    In this case, the developer is clearly assuming that o will always be an Animal, and as long as their assumption is correct, everything works fine. But if they're wrong, then what they end up with here is a NullReferenceException. With a regular cast, they would have gotten an InvalidCastException instead, which would have more correctly identified the problem.

    Sometimes, this bug can be hard to find:

    class Foo{
        readonly Animal animal;
    
        public Foo(object o){
            animal = o as Animal;
        }
    
        public void Interact(){
            animal.Speak();
        }
    }
    

    This is another case where the developer is clearly expecting o to be an Animal every time, but this isn't obvious in the constructor, where the as cast is used. It's not obvious until you get to the Interact method, where the animal field is expected to be positively assigned. In this case, not only do you end up with a misleading exception, but it isn't thrown until potentially much later than when the actual error occurred.

    In summary:

    • If you only need to know whether or not an object is of some type, use is.

    • If you need to treat an object as an instance of a certain type, but you don't know for sure that the object will be of that type, use as and check for null.

    • If you need to treat an object as an instance of a certain type, and the object is supposed to be of that type, use a regular cast.

提交回复
热议问题