Thursday, July 16, 2009

Casting Call

Type safety only gets you so far; eventually you have to cast. There are three features in the C# language which address typing: the unary cast operator and the binary "as" and "is" operators. I see people misuse these operators all the time, so here for your records are the official Best Ways to use each.

If you want to check the type of an object and do not care about using the object as that type, use the "is" operator. For example:

if (thing is MyType) {
    // do something which doesn't involve thing

If you want to check the type of an object and then use that object as that type, use the "as" operator and the check for null. For example:

var my_type_thing = thing as MyType;
if (my_type_thing != null) {
    // do something with my_type_thing

This only works for reference types since value types cannot be null. For value types, use the "is" and cast operators. For example:

if (thing is MyValueType) {
    var my_value_type_thing = (MyValueType)thing;
    // do something with my_value_type_thing

If you know for a fact that an object is some type, use the cast operator. For example:

var my_type_thing = (MyType)thing;
// do something with my_type_thing

These patterns minimize the operations performed by the runtime. This wisdom comes by way Marek who educated me on this a while ago. Please pass it on.


Jonathan Pryor said...

Two more for you

1. You can use 'as' with nullable types, thus allowing use of value types without an 'is'/cast combination:

var a = o as int?;
if (a.HasValue) {...}

2. foreach() implicitly inserts a cast operation to the control variable type. This is most noticeable when using 1.0 collections:

ArrayList strings = GetStrings();
foreach (var s in strings) {/* s is an Object! */}

foreach (string s in strings) {
/* s is string, but you may get an
InvalidCastException if the cast fails */

knocte said...

I was working on a Gendarme rule to detect these bad cases. I wrote a first version, but lazyness made me not to insist on it (I got some reviews on the gendarme list, but never got back to them; so you're more than welcome if you want to step up).

Sandro Magi said...

Using "as" and checking for null is no more efficient than than checking the type via "is" and then casting using "as".

Furthermore, it's less safe since you have a potentially null reference available in the outer scope which may be inadvertently dereferenced elsewhere. I cringe whenever it, and I really hope this bad advice just dies already.