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.