Of Is and As (Operators in C#)

It’s been a while since I made any .NET posts, but here’s one that’s been bubbling up for too long!

I have to confess to being a little bit ‘militant’ to certain things I see in code. Blindly catching `Exception` is one thing that gives me shivers, using the `as` operator everywhere is another.

The issue I take is one of code semantics and intent, and consequently readability. For example, take the following piece of C# code:

Person personPaul = ...;Man paul = (Man)personPaul;

When I read the above, I take it to read that I fully expect to be able to treat `personPaul` as a `Man`, that `personPaul` categorically can be cast successfully.

However, the following is subtly different:

Man paul = personPaul as Man;

This statement uses the `as` operator which attempts to perform the specified cast, but, if it fails instead of throwing an `InvalidCastException`, it will return `null`. In the example above, the use of `as` implies a statement along the lines of “I’d like to treat `personPaul` as a man, but if I can’t, I’m not too worried”. If we know that an object is of type `T`, I don’t see the need to use a cast that handles a situation when it isn’t.

Where the `as` operator is intended for use is in situations to avoid the expensive CLR checks associated with casting in a code pattern such as this:

if (personPaul is Man) {    Man paul = (Man)personPaul;}

In this situation we’re actually performing two casting operations:

L_0000: ldarg.0 L_0001: isinst ManL_0006: brfalse.s L_002fL_0008: ldarg.0 L_0009: castclass Man

Notice the calls to `isinst` and `castclass`—in both cases the CLR will perform a check to determine whether the object in question is cast-able. Since we definitely know by line `L_0009` we’re looking at a `Man` (since we’ve already performed one cast operation on `IL_0001` that returns us from the routine if it fails), there’s no need for the CLR to perform an additional check.

In these situations, it’s much better to use the `as` operator as mentioned earlier, which ultimately results in something similar to:

L_0000: ldarg.0 L_0001: isinst ManL_0006: stloc.0 L_0007: ldloc.0 L_0008: brfalse.s L_002cL_000a: ldloc.0 L_000b: call void Test::DoSomething(man)

In this case we’re only doing the cast once, reducing the number of expensive CLR type safety checks. Admittedly, there are far more things that could cause your code to slow down but the key benefit for me is that it makes the code easier to read and any invalid operations will result in a far more meaningful `InvalidCastException`, rather than a `NullReferenceException`.

Incidentally, one other important note that I stumbled across is that casting using the `as` operator will not use any conversions that you specify, that would normally be performed when using casting syntax.