Double-Casting Anti-Pattern

published: Fri, 24-Mar-2006   |   updated: Fri, 24-Mar-2006
4th July fireworks at Avon, CO

There's a coding anti-pattern in C# that's very prevalent but inefficient. I'm going to guess its popularity is due to the fact that most C# developers have come to C# from a non-managed code language, such as C++ or Delphi. I'm referring to the double-casting anti-pattern.

Here's some representative code:

    public static int DoubleCast(object obj) {
      if ((obj != null) && (obj is Foo))
        return (Foo)obj.Length;
      return -1;

We're passing in an object instance which may or may not be of type Foo. If it is not null and of type Foo, we should return the value of its Length property, if not we should return -1.

The normal developer codes that requirement up pretty much as it stands. Using the is operator, he checks to see if the passed-in non-null object is a Foo, and if so he casts the object to a Foo to get at its Length property and then returns its value. Otherwise he returns -1. Pretty simple.

Except he's doing two typecasts. The first one is performed by the is operator, and the second one by the explicit typecast. Both of them ask the CLR to do some non-trivial work to determine if the cast is safe to satisfy .NET's rigid type-safety. In IL, the call to is calls the isinst operation. This essentially calls the castclass operation to try and cast the instance to the required type. castclass ascertains the instance's type by asking it and then queries the type database in the program to see whether that type can be converted to the required type. If it can, it returns an instance of the required type (which in fact is a reference to the original object). If it can't do that, it returns null. The IL for the is operator then checks the return value to be non-null to produce a boolean result.

The second typecast is where developers usually trip up. In non-managed code like C++ or Delphi, such a typecast merely "pretends" that the object instance is of the required type; no actual code is executed. This is because C++ and Delphi in these situations is acting as a weakly-typed language. But in managed code, it's totally different. Instead the code is compiled into a call to the castclass IL operation again. And again the CLR queries the type database, etc, etc, etc. Time passes doing stuff we've just done.

So, in C#, this fairly normal-looking code hides an inefficiency. An inefficiency which, as a matter of fact, is extremely easy to fix. And the way to fix it is to "unravel" the is operator:

    public static int SingleCast(object obj) {
      Foo foo = obj as Foo;
      if (foo != null)
        return foo.Length;
      return -1;

First we typecast the passed-in object to an instance of type Foo using the as operator. This will either succeed (we'll get a reference) or it won't (we'll get null). So we check the resulting reference to be non-null and if it is we just return the value of the Length property. Otherwise we return -1 as before. Notice though that we have just the one typecast. Since the time spent in typecasting is essentially the majority of time spent in this routine, we've just doubled its efficiency.

The other nice thing about typecasting with the as operator is that typecasting a null reference just returns a null reference anyway, so the above code removes the check for the passed-in object to be non-null.

Notice we can't use the normal cast expression since that will throw an exception if the instance cannot be cast to the requireed type.

So, in general, your code shouldn't be using the is operator because, in general, code that does so will immediately perform a typecast. And as we've seen, that's doing twice the work you need to do.