Double-Casting Anti-Pattern
published: Fri, 24-Mar-2006 | updated: Fri, 24-Mar-2006
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.
