Wednesday, June 04, 2008

When NOT to use the C# "as" keyword

Both C++ and C# offer the cast () operator.  C# also offers the "as" operator syntax which does almost the same thing and is considered by some to look prettier and be more C#'ish.  But using the "as" keyword has downsides that you may not know about.

Consider the following two methods. Please skip the question of "Why would you take an object and cast to a string instead of just casting to a string?" as this is a contrived example to illustrate the differences between () and the as keyword, and in real applications there are many different reasons for casting down.

class SomeType {
    bool IsTooLong1(object obj) {
        string someString = obj as string;
        return someString.Length > 20;
    }

    bool IsTooLong2(object obj) {
        string someString = (string)obj;
        return someString.Length > 20;
    }
}

If you pass an object that is not a string, IsTooLong1 will throw a NullReferenceException whereas IsTooLong2 will throw an InvalidCastException. Obviously an InvalidCastException helps you more when you are debugging to find the problem as it tells you immediately that the wrong type of object was passed to something. The NullReferenceException could be thrown if you passed null into the method or the wrong type of object, so besides the fact that NullReferenceException doesn't immediately suggest that it could be a bad cast, it also leaves ambiguity as to the cause of the exception.

Why the difference? The "as" keyword attempts to cast the object and if the cast fails null is returned silently. The () cast operator will throw an exception immediately if the cast fails.

There are good applications to use each operator. The above shows an example of where the () operator is more appropriate. But here's an example of where the "as" keyword is better:

class SomeType {
    int someField;
    // The numeric suffixes on these methods are only added for reference later
    public override bool Equals1(object obj) {
        SomeType other = obj as SomeType;
        if (other == null) return false;
        return someField == other.SomeField;
    }
    public override bool Equals2(object obj) {
        if (obj == null) return false;
        // protect against an InvalidCastException
        if (!(obj is SomeType)) return false;
        SomeType other = (SomeType)obj;
        return someField == other.SomeField;
    }
}

The Equals1 method above is more efficient (and easier to read) than Equals2, although they get the same job done. While Equals1 compiles to IL that performs the type checking and cast exactly once, Equals2 compiles to do a type comparison first for the "is" operator, and then does a type comparison and cast together as part of the () operator. So using "as" in this case is actually more efficient. The fact that it is easier to read is a bonus.

In conclusion, only use the C# "as" keyword where you are expecting the cast to fail in a non-exceptional case. If you are counting on a cast to succeed and are unprepared to receive any object that would fail, you should use the () cast operator so that an appropriate and helpful exception is thrown.

12 comments:

  1. It seems that you should be using "as" along with checking for null. IMO using as is just a better way of avoiding cast exceptions. It doesn't "fail silently", it returns null.

    ReplyDelete
  2. Here's an example of where it is better to use regular casting:

    I create a Button, and hook up an event handler to it. In the event handler, I want to cast the sender to Button.

    Since I have only wired up a button to that event handler, I should cast using (). It should not be possible for anything else to come in on sender; if it does, that is certainly an exception.

    Why would I want to bother casting using "as" and checking for null? I shouldn't have to bother, I know what's hooked up.

    ReplyDelete
  3. I was almost going to agree with you. But if "as" fails and returns null, obviously a default Visual Studio configuration is going to break the execution and throw the error so we are going to look at it. It all boils down to how you have handled the rest of your code.

    ReplyDelete
  4. @Nick: In the case of the sender object going thru a button event handler, would there be any need to check for null? As you said, you know what's hooked up.

    To my mind using the "as" operator is much easier to read but this post and comments have been really interesting. Thx!

    ReplyDelete
  5. Sometimes you know the cast will succeeded. Consider
    HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest
    In cases like that as is useful.

    ReplyDelete
  6. null propagation is insidious. null checks are also insidious-- they are ugly, add complexity, and fragile. I think the readability improvements of "as" over "()" cast syntax are minor compared to the cost of null propagation. Thus, "as" should be reserved for the very specific cases (for example as the author mentioned). Both @morbii and @jarek seem to be advocating otherwise.

    As the author mentions, people seem to prefer the "as" for the minor cosmetic improvement, and so it contributes to more fragile code. This is a C# language syntax problem. The () syntax should have been dropped to begin with and an equally appealing syntax for the "cast and throw if invalid" language behavior should've been adopted. Alas.

    Steve

    ReplyDelete
  7. In both cases the result is null, wrong explanation. in both cases the exception is thrown

    "Object reference not set to an instance of an object."

    So if you are using any of the given casting please assure to have a check for null value

    ReplyDelete
  8. Rahul,
    I'm not sure I'm understanding you correctly, but if you're saying that the () cast operator results in null when the cast fails, you are incorrect. It's possible that null is the result if the original value you were casting was already null, of course.

    ReplyDelete
  9. @Steve Ash and @Andrew, I am solidly in your camp, based on the fact that you are both correct. There is a dev where I work who has a reputation for fanciful imaginings about coding. He claims that using the "as" keyword is more performant. To me, this doesn't matter, it's not a question of performance, it's a question of idiomatic and good-practice code. Any credence to this dev's claims?

    ReplyDelete
  10. @Bryan, "as" is *not* more performant than () by itself. But "as" is more performant than "is" followed by "()". So if you have code that needs a no-throw type cast, use "as" (and check for a null result) for more performant code. The reason "as" is faster here is because both "is" and "()" perform CLR type checks (2 type checks total), but "as" performs only one type check, and the additional null check later in your code is trivial, so 'as' is faster overall. But only if you would have done a double-type check otherwise.

    ReplyDelete
  11. Andrew you are wrong, source: http://stackoverflow.com/questions/4586894/c-sharp-cast-exception

    the reason why explicit casting with () is MANY times slower has to do with the fact that exceptions are crazy SLOW in CLR!

    the only reason to use () cast is when the types expected are static and not dynamic. in such case you can rely on the compiler to give you ERROR, so you cannot even compile the nonsense you have written, example:
    class A{}
    class B{}

    A a = new A(); B b = new B();
    a = (B)b;

    this wont compile!

    as to the conclusion: "Obviously an InvalidCastException helps you more when you are debugging ..." what you are looking for when solving a bug is the cause, not the symptom! in this way of thinking, whether a have a null or cast exception will help little, cause you still have to go to the line of code and debug from beginning (or browse stack trace). finding that null exception is due to failed casting 'as' should take you no more than a few seconds!

    ReplyDelete
  12. root@boy, I think you missed the point of the StackOverflow question you refer to. Explicit casting with the () operator is very fast! It's not casting that's slow. It's the exceptions that are costly. And as this post says, if you're expecting that the cast may fail in legitimate scenarios then throwing exceptions would be inappropriate, and you'd want to use the "is" or "as" C# operators. But if you only expect one type, use the () operator because that will throw a very helpful exception that tells you exactly what type you were casting to, *and from*, and where it took place so you can figure out where the value came from. Contrast this with your argument that NullReferenceException is just as good, which gives you less information, much later after the problem enters your program, and is still an exception that would arguably hurt the perf of your program if it happens many times in a short period.

    ReplyDelete