Casting vs "as" - embracing exceptions

(I've ended up commenting on this issue on Stack Overflow quite a few times, so I figured it would be worth writing a blog post to refer to in the future.)

There are lots of ways of converting values from one type to another – either changing the compile-time type but actually keeping the value the same, or actually changing the value (for example converting int to double). This post will not go into all of those - it would be enormous - just two of them, in one specific situation.

The situation we're interested in here is where you have an expression (typically a variable) of one reference type, and you want an expression with a different compile-time type, without changing the actual value. You just want a different "view" on a reference. The two options we'll look at are casting and using the "as" operator. So for example:

object x = "foo"
string cast = (string) x; 
string asOperator = x as string;

The major differences between these are pretty well-understood:

  • Casting is also used for other conversions (e.g. between value types); "as" is only valid for reference type expressions (although the target type can be a nullable value type)
  • Casting can invoke user-defined conversions (if they're applicable at compile-time); "as" only ever performs a reference conversion
  • If the actual value of the expression is a non-null reference to an incompatible type, casting will throw an InvalidCastException whereas the "as" operator will result in a null value instead

The last point is the one I'm interested in for this post, because it's a highly visible symptom of many developers' allergic reaction to exceptions. Let's look at a few examples.

Use case 1: Unconditional dereferencing

First, let's suppose we have a number of buttons all with the same event handler attached to them. The event handler should just change the text of the button to "Clicked". There are two simple approaches to this:

void HandleUsingCast(object sender, EventArgs e) 

    Button button = (Button) sender; 
    button.Text = "Clicked"
}

void HandleUsingAs(object sender, EventArgs e) 

    Button button = sender as Button; 
    button.Text = "Clicked"
}

(Obviously these aren't the method names I'd use in real code - but they're handy for differentiating between the two approaches within this post.)

In both cases, when the value of "sender" genuinely refers to a Button instance, the code will function correctly. Likewise when the value of "sender" is null, both will fail with a NullReferenceException on the second line. However, when the value of "sender" is a reference to an instance which isn't compatible with Button, the two behave differently:

  • HandleUsingCast will fail on the first line, throwing a InvalidCastException which includes information about the actual type of the object
  • HandleUsingAs will fail on the second line with a NullReferenceException

Which of these is the more useful behaviour? It seems pretty unambiguous to me that the HandleUsingCast option provides significantly more information, but still I see the code from HandleUsingAs in examples on Stack Overflow... sometimes with the rationale of "I prefer to use as instead of a cast to avoid exceptions." There's going to be an exception anyway, so there's really no good reason to use "as" here.

Use case 2: Silently ignoring invalid input

Sometimes a slight change is proposed to the above code, checking for the result of the "as" operator not being null:

void HandleUsingAs(object sender, EventArgs e) 

    Button button = sender as Button; 
    if (button != null
    { 
        button.Text = "Clicked"
    } 
}

Now we really don't have an exception. We can do this with the cast as well, using the "is" operator:

void HandleUsingCast(object sender, EventArgs e) 

    if (sender is Button) 
    { 
        Button button = (Button) sender; 
        button.Text = "Clicked"
    } 
}

These two methods now behave the same way, but here I genuinely prefer the "as" approach. Aside from anything else, it's only performing a single execution-time type check, rather than checking once with "is" and then once again with the cast. There are potential performance implications here, but in most cases they'd be insignificant - it's the principle of the thing that really bothers me. Indeed, this is basically the situation that the "as" operator was designed for. But is it an appropriate design to start with?

In this particular case, it's very unlikely that we'll have a non-Button sender unless there's been a coding error somewhere. For example, it's unlikely that bad user input or network resource issues would lead to entering this method with a sender of (say) a TextBox. So do you really want to silently ignore the problem? My personal view is that the response to a detected coding error should almost always be an exception which either goes completely uncaught or which is caught at some "top level", abandoning the current operation as cleanly as possible. (For example, in a client application it may well be appropriate to terminate the app; in a web application we wouldn't want to terminate the server process, but we'd want to abort the processing of the problematic request.) Fundamentally, the world is not in a state which we've really considered: continuing regardless could easily make things worse, potentially losing or corrupting data.

If you are going to ignore a requested operation involving an unexpected type, at least clearly log it - and then ensure that any such logs are monitored appropriately:

void HandleUsingAs(object sender, EventArgs e) 

    Button button = sender as Button; 
    if (button != null
    { 
        button.Text = "Clicked"
    } 
    else 
    { 
        // Log an error, potentially differentiating between
        // a null sender and input of a non-Button sender.
    } 
}

Use case 3: consciously handling input of an unhelpful type

Despite the general thrust of the previous use case, there certainly are cases where it makes perfect sense to use "as" to handle input of a type other than the one you're really hoping for. The simplest example of this is probably equality testing:

public sealed class Foo : IEquatable<Foo> 

    // Fields, of course

    public override bool Equals(object obj) 
    { 
        return Equals(obj as Foo); 
    } 

    public bool Equals(Foo other) 
    { 
        // Note: use ReferenceEquals if you overload the == operator
        if (other == null
        { 
            return false
        } 
        // Now compare the fields of this and other for equality appropriately.
    } 

    // GetHashCode etc
}

(I've deliberately sealed Foo to avoid having to worry about equality between subclasses.)

Here we know that we really do want to deal with both null and "non-null, non-Foo" references in the same way from Equals(object) - we want to return false. The simplest way of handling that is to delegate to the Equals(Foo) method which needs to handle nullity but doesn't need to worry about non-Foo reference.

We're knowingly anticipating the possibility of Equals(object) being called with a non-Foo reference. The documentation for the method explicitly states what we're meant to do; this does not necessarily indicate a programming error. We could implement Equals with a cast, of course:

public override bool Equals(object obj) 

    return obj is Foo && Equals((Foo) obj); 
}

... but I dislike that for the same reasons as I disliked the cast in use case 2.

Use case 4: deferring or delegating the decision

This is the case where we pass the converted value on to another method or constructor, which is likely to store the value for later use. For example:

public Person CreatePersonWithCast(object firstName, object lastName) 

    return new Person((string) firstName, (string) lastName); 
}

public Person CreatePersonWithAs(object firstName, object lastName) 

    return new Person(firstName as string, lastName as string); 
}

In some ways use case 3 was a special case of this, where we knew what the Equals(Foo) method would do with a null reference. In general, however, there can be a significant delay between the conversion and some definite impact. It may be valid to use null for one or both arguments to the Person constructor - but is that really what you want to achieve? Is some later piece of code going to assume they're non-null?

If the constructor is going to validate its parameters and check they're non-null, we're essentially back to use case 1, just with ArgumentNullException replacing NullReferenceException: again, it's cleaner to use the cast and end up with InvalidCastException before we have the chance for anything else to go wrong.

In the worst scenario, it's really not expected that the caller will pass null arguments to the Person constructor, but due to sloppy validation the Person is constructed with no errors. The code may then proceed to do any number of things (some of them irreversible) before the problem is spotted. In this case, there may be lasting data loss or corruption and if an exception is eventually thrown, it may be very hard to trace the problem to the original CreatePersonWithAs parameter value not being a string reference.

Use case 5: taking advantage of "optional" functionality

This was suggested by Stephen Cleary in comments, and is an interesting reversal of use case 3. The idea is basically that if you know an input implements a particular interface, you can take a different - usually optimized - route to implement your desired behaviour. LINQ to Objects does this a lot, taking advantage of the fact that while IEnumerable<T> itself doesn't provide much functionality, many collections implement other interfaces such as ICollection<T>. So the implementation of Count() might include something like this:

ICollection<T> collection = source as ICollection<T>;
if (collection != null)
{
    return collection.Count;
}
// Otherwise do it the long way (GetEnumerator / MoveNext)

Again, I'm fine with using "as" here.

Conclusion

I have nothing against the "as" operator, when used carefully. What I dislike is the assumption that it's "safer" than a cast, simply because in error cases it doesn't throw an exception. That's more dangerous behaviour, as it allows problems to propagate. In short: whenever you have a reference conversion, consider the possibility that it might fail. What sort of situation might cause that to occur, and how to you want to proceed?

  • If everything about the system design reassures you that it really can't fail, then use a cast: if it turns out that your understanding of the system is wrong, then throwing an exception is usually preferable to proceeding in a context you didn't anticipate. Bear in mind that a null reference can be successfully cast to any nullable type, so a cast can never replace a null check.
  • If it's expected that you really might receive a reference of the "wrong" type, then think how you want to handle it. Use the "as" operator and then test whether the result was null. Bear in mind that a null result doesn't always mean the original value was a reference of a different type - it could have been a null reference to start with. Consider whether or not you need to differentiate those two situations.
  • If you really can't be bothered to really think things through (and I hope none of my readers are this lazy), default to using a cast: at least you'll notice if something's wrong, and have a useful stack trace.

As a side note, writing this post has made me consider (yet again) the various types of "exception" situations we run into. At some point I may put enough thought into how we could express our intentions with regards to these situations more clearly - until then I'd recommend reading Eric Lippert's taxonomy of exceptions, which has certainly influenced my thinking.

Published Thu, Sep 19 2013 18:38 by skeet
Filed under: ,

Comments

# re: Casting vs "as" - embracing exceptions

In Use Case 1 - should  HandleUsingAs actually use "as" and not a cast?

Thursday, September 19, 2013 12:44 PM by Reed Copsey, Jr.

# re: Casting vs "as" - embracing exceptions

Shouldn't

void HandleUsingAs(object sender, EventArgs e)  

{  

   Button button = (Button) sender;  

   button.Text = "Clicked";  

}

Rather be

void HandleUsingAs(object sender, EventArgs e)  

{  

   Button button = sender as Button;  

   button.Text = "Clicked";  

}

?

Thursday, September 19, 2013 12:47 PM by Gilles

# re: Casting vs "as" - embracing exceptions

@Gilles and Reed: Yes, doh. This is what I get for blogging while travelling - but it should all be okay now.

Thursday, September 19, 2013 12:48 PM by skeet

# re: Casting vs "as" - embracing exceptions

I may just be dense here, but in your Use Case 1, why has the HandleUsingAs got the line

    Button button = (Button) sender; 

Instead of

    Button button = sender as Button

The method name suggests it should, and it appears that he code in both is currently identical.

Cheers

Thursday, September 19, 2013 12:50 PM by Alex White

# re: Casting vs "as" - embracing exceptions

@Alex: Not dense at all - just a somewhat significant typo, now fixed.

Thursday, September 19, 2013 12:52 PM by skeet

# re: Casting vs "as" - embracing exceptions

While I agree wholeheartedly with what's written here, the "case 2" with "HandleUsingAs" has always kind of bothered me.

I know from performance (usually not _that_ big of an issue, especially if it's in GUI event handling code such as in this case) perspective, but even from a "agghgh why do this _twice_?" principle perspective it makes sense and I agree.

But I've always been bothered with the scoping of the local button variable. Like, its usage in this case is completely scoped to within the "if" block. I don't want it to be accessible outside, it has no meaning outside (indeed, it can be "null" outside). By the time my "if" block closes, I feel like that variable should be "out of scope" and inaccessible. The good thing with the is/cast style is that this scope is maintained. (There are other considerations for sure, like if instead of "sender" you were checking a class field/property and avoiding hitting it twice or having it change values/types between the "is" check and the cast)

I'm not saying it should necessarily be favoured, just that when faced with the case of "my object MIGHT be MyType, and if it is, treat it like MyType and do this with it" I've never felt "at home" with any of the options short of refactoring/redesigning the dilemma away completely.

Thursday, September 19, 2013 1:45 PM by Chris Sinclair

# re: Casting vs "as" - embracing exceptions

In use case 4, wouldn't the desired outcome be an ArgumentNullException?  InvalidCastException in that case would be vague as to which value, either firstname or lastname, was invalid in the first place.

This is, of course, assuming that the Person constructor did do proper validations of its parameters.  In those cases, it would specify which parameter was invalid where as an InvalidCastException would require additional tracing and debugging.

Thursday, September 19, 2013 2:04 PM by swiftfoxmark2

# re: Casting vs "as" - embracing exceptions

I've been meaning to write this same thing for 2 years, you beat me to it somehow!

Thursday, September 19, 2013 2:34 PM by Mike Mooney

# Use case 5: Enhanced behavior?

How about a fifth use case where a type can be checked whether it inherits a particular interface that overrides the generic behavior?

In that case, I find the as-cast most useful, since the cast exception just gets in the way.

E.g., ISupportErrorInfo, ISerializable, etc.

Thursday, September 19, 2013 3:57 PM by Stephen Cleary

# re: Casting vs "as" - embracing exceptions

@Chris: Yes, the scoping bothers me too. I have a solution to it - but I don't think you'll like it...

stackoverflow.com/.../7113387

Personally I think we're actually missing a language construct for "as/if combined". And yes, it's usually a design smell to get there, but sometimes it's very hard to avoid.

Thursday, September 19, 2013 4:03 PM by skeet

# re: Casting vs "as" - embracing exceptions

@swiftfoxmark2: No, ArgumentNullException wouldn't be good - because the problematic argument (to the CreatePerson method) wouldn't be null, it would be a value of the wrong type.

I completely agree that there's benefit in ArgumentNullException having the parameter name - but then you would still have additional tracing and debugging to work out *why* it was null. Swings and roundabouts...

Thursday, September 19, 2013 4:05 PM by skeet

# re: Casting vs "as" - embracing exceptions

@Stephen: Good point. I think that's a sort of mirror image of use case 3 - consciously handling an "optionally helpful" type. (LINQ to Objects uses this in a bunch of places.)

I'm half tempted to add it into the post retrospectively now... with credit, of course :)

And yes, I'd use "as" there as well - but I'd prefer to have something which didn't clutter the scope.

Thursday, September 19, 2013 4:06 PM by skeet

# re: Casting vs "as" - embracing exceptions

Last time I checked, using as was slower than using is followed by a cast. I assumed the pattern is common enough that there is some sort of compiler or JIT optimization for it.

Thursday, September 19, 2013 4:40 PM by Jonathan Allen

# re: Casting vs "as" - embracing exceptions

@Jon Skeet: You are an evil, evil man. I love it.

Also, I just realized, you coooould wrap the whole section with a curly brace scope block. But it's just so rare to see them without a preceding if/for/foreach/switch/etc I don't know if it'd be much of an improvement; probably invoke a "WTF did they do that?" during code review.

Thursday, September 19, 2013 4:47 PM by Chris Sinclair

# re: Casting vs "as" - embracing exceptions

@Jonathan: Interesting - I wonder when that happened, because I'm sure it *used* to be significantly slower. Note that for nullable value types, using "as" is *much* slower. (With reference types, my ad hoc tests just now show casts and "as" being pretty much a dead heat.)

Thursday, September 19, 2013 4:51 PM by skeet

# Use case 5: Enhanced behavior?

@Jon: Sure! I know what you mean about cluttering the scope. C++ was updated to support this (I don't know about Java):

if (int x = ...) { /* x is in scope */ }

But I don't think that really feels right for C#.

Thursday, September 19, 2013 6:31 PM by Stephen Cleary

# re: Casting vs "as" - embracing exceptions

Isn't ClassCastException the java exeption? I thought the C# exception was InvalidCastException.

Friday, September 20, 2013 1:58 AM by Zachary Snow

# re: Casting vs "as" - embracing exceptions

Typo:

public void CreatePersonWithCast

-->

public Person CreatePersonWithCast

Friday, September 20, 2013 2:25 AM by Nitpicker

# re: Casting vs "as" - embracing exceptions

@Zachary: Yes, I always get them mixed up. I diligently checked early on - and then forgot the result later on! Fixed.

@Nitpicker: Fixed.

Friday, September 20, 2013 3:43 AM by skeet

# Interesting Finds: September 20, 2013

Interesting Finds: September 20, 2013

Friday, September 20, 2013 7:38 AM by Jason Haley

# re: Casting vs "as" - embracing exceptions

Thanks for fixing typos, I can now send this link to my newbies colleagues!

PS: btw, I wasn't aware of the sic("as" only ever performs a reference conversion) thing. I suppose this is due to how "is" is working.

Friday, September 20, 2013 9:52 AM by Olivier

# re: Casting vs "as" - embracing exceptions

@Olivier: "as" only performs "reference conversion" (really, for anything nullable since this includes `Nullable<T>`) not because of how "is" behaves, but because if it's incompatible it assigns `null`. If you attempted to use `int i = myObj as int;` and it fails, what should the result be? Can't really be `default(int)` as that would be `0` and you wouldn't be able to discern between a failed cast and a valid cast where the value actually was `0`.

Friday, September 20, 2013 4:23 PM by Chris Sinclair

# re: Casting vs "as" - embracing exceptions

This article somehow reminds me of the "Design by Contract" principle.

Friday, September 20, 2013 6:45 PM by OJ Raqueño

# re: Casting vs "as" - embracing exceptions

My motto is:

Exceptions are not Pokémon.  You do not need to catch 'em all.

Tuesday, September 24, 2013 8:12 AM by Trevor E.

# re: Casting vs "as" - embracing exceptions

Personally in case 2 I prefer to still cast. To me it reads better, "If it is a string, cast it to a string." over, "Assign the value as a string, if the assigned value is not null then..."

To me the ideal case for 'as' is when you have a default value.

var str = result as string ?? "Default Case";

I would love it if you were forced to explicitly state the default as when you use 'as'. Even if 90% of the time you just set it to null, I think

Button button = sender as Button ?? null;

reminds people that the value can indeed be null and should be handled.

The extra benefit of an explicit default value is you could then use 'as' with Value types.

int num = obj as int ?? 0;

Wednesday, September 25, 2013 12:13 AM by Chris