I first tried to present this as an emotional rant on how no one seems to understand the subtle differences between the cast operators in C#. That proved to be rather long and uninteresting, so instead, I will succintly present their correct usage in hope some might stumble unto this article somehow and it might alleviate their ignorance.
1. The normal way to do casts in C# is to use the cast operator, i.e. "(<TypeName>)". Example:
2. Now what if it was normal in the program that some of the time, Foo was passed something other then a Dog? What if it was not necessarily an error? Well the "is" operator allows us to verify this and avoid an unnecessary exception.
So that's all we really need to know about casts. If we can assume the cast will succeed, then we go right ahead and just cast - if that fails, it means our assumption is wrong and therefore something is probably awry: as is appropriate in such conditions, an exception will thrown, specifically an InvalidCastException. If on the contrary we do not know the cast will succeed, we can first test if the type is what we expect with "is".
... Except that the latter pattern is rather ugly. Specifically, we end up writing the expected type twice, and causing the code to perform two typechecks at run-time instead of one: one for the "is", one for the cast (which has to typecheck since it must throw an exception if it's invalid).
3. This is why the kind and caring C# language designers have condensed this small ugly pattern into one convenient, but oh so abused operator: "as". The purpose of the "as" operator is to express in one line and with only one check what we were doing with two using "is" and a cast: test if the object is of the expected type, if so cast it, if not don't cast it. Actually "as" returns null if the cast fails. So our "is" + cast example turns into this:
The confusion exists because many programmers think of "as" as a somehow improved version of the cast operator that improves performance because it doesn't throw exceptions or something else that doesn't make sense. So they'll gleefully write such nonsense as:
This makes me want to cry though:
TL;DR, here's what every C# programmer should know about casts:
1) If you can assume the object is of the given type, use a normal cast.
2) If you cannot assume the object is of the given type, use "as" and check for null to verify if the cast succeeded.
Conversely:
1) Don't use "as" if you're sure the conversion will succeed, because it makes your intent unclear
2) Always null check after using "as". If you're not checking for null, then either add the check or use a normal cast.
There. I've said it all.
1. The normal way to do casts in C# is to use the cast operator, i.e. "(<TypeName>)". Example:
void Foo(object bar) {
var dog = (Dog)bar;
dog.Bark(); // woof!
}
This code tells me: I know bar is always a Dog, so I can safely cast it to the Dog type and ask it to bark. If bar is not a Dog, then that's an error in the program and it will throw an exception appropriately, since that shouldn't be happening.2. Now what if it was normal in the program that some of the time, Foo was passed something other then a Dog? What if it was not necessarily an error? Well the "is" operator allows us to verify this and avoid an unnecessary exception.
void Foo(object bar) {
if (bar is Dog) {
var dog = (Dog)bar;
dog.Bark(); // woof!
}
}
So that's all we really need to know about casts. If we can assume the cast will succeed, then we go right ahead and just cast - if that fails, it means our assumption is wrong and therefore something is probably awry: as is appropriate in such conditions, an exception will thrown, specifically an InvalidCastException. If on the contrary we do not know the cast will succeed, we can first test if the type is what we expect with "is".
... Except that the latter pattern is rather ugly. Specifically, we end up writing the expected type twice, and causing the code to perform two typechecks at run-time instead of one: one for the "is", one for the cast (which has to typecheck since it must throw an exception if it's invalid).
3. This is why the kind and caring C# language designers have condensed this small ugly pattern into one convenient, but oh so abused operator: "as". The purpose of the "as" operator is to express in one line and with only one check what we were doing with two using "is" and a cast: test if the object is of the expected type, if so cast it, if not don't cast it. Actually "as" returns null if the cast fails. So our "is" + cast example turns into this:
void Foo(object bar) {
var dog = bar as Dog;
if (dog != null) {
dog.Bark();
}
}
This, semantically, is exactly the same. We're fully expecting bar not to be a Dog at least some of the time, and that's perfectly normal, so we don't want to throw an exception in that case: we just won't ask it to bark and that'll be it. If it's a Dog, fine, bark; if it's not, that's fine too, just don't bark then.The confusion exists because many programmers think of "as" as a somehow improved version of the cast operator that improves performance because it doesn't throw exceptions or something else that doesn't make sense. So they'll gleefully write such nonsense as:
void Foo(object bar) {
var dog = bar as Dog;
dog.Bark(); // This is just wrong
}
Oh wow this code won't throw an InvalidCastException on the first line, but instead it'll throw a potentially much more obscure NullReferenceException on the second line, because if bar is not a Dog then dog will be null. What an improvement! This just makes debugging more difficult because it introduces another indirection to solve, i.e. why is dog null here? At least in debug builds, software should fail as fast and violently as possible. It's much easier to prove someone guilty if you catch him red-handed.This makes me want to cry though:
void Foo(object bar) {
if (bar is Dog) {
var dog = bar as Dog;
dog.Bark();
}
}
So since you're sure bar is a Dog inside the if statement, why do you use an operator that says "I am not sure this is a Dog"? And then assume the cast succeeded?TL;DR, here's what every C# programmer should know about casts:
1) If you can assume the object is of the given type, use a normal cast.
2) If you cannot assume the object is of the given type, use "as" and check for null to verify if the cast succeeded.
Conversely:
1) Don't use "as" if you're sure the conversion will succeed, because it makes your intent unclear
2) Always null check after using "as". If you're not checking for null, then either add the check or use a normal cast.
There. I've said it all.









Anyway, good post, clear and to the point. Hopefully any C# programmer that didn't know basics like this will put down the compiler and pick up a book though