Welcome Guest! To access all forums & features, please register an account or sign-in. → Why register?




Photo - - - - -

Casts in C#

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:

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.



What sort of programmers are you working with who don't know this? :huh:

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 :p.

ZakO, on 18 August 2012 - 09:34, said:

What sort of programmers are you working with who don't know this?
You'd be amazed. Some of my colleagues are former C++ programmers, and C++ programmers are taught the regular casts are to be avoided in favor of the more "advanced" casts in the language (static_cast<> etc.). I suppose when they look at C# they tend to assume it's the same thing with the (<typename>) cast. They're also taught exceptions are bad because exceptions are **** in C++ (like pretty much all the features), so between something that throws vs something that doesn't they'll tend much more towards the latter.

But yeah it's just ignorance. I see this so often (actually I've rarely seen "as" used correctly) I had to express my feelings. :angry:
So the problem is C++ programmers? :p

But it's pretty simple really. When transitioning to a new language, it's important to find a book or other materials which teach you the language while contrasting with what you already know, e.g. "C# for C++ Programmers". In this way, you correct any wrong assumptions you had and use the language correctly. However, if you pick up a generic C# book made for everybody, or learn directly from the language specification (as I often do), you may miss little aspects like these.

Xinok, on 18 August 2012 - 16:49, said:

So the problem is C++ programmers? :p
It's always C++'s fault one way or another :p
Good read. I find it hard to comprehend that something like this would be so common, surely the difference would be part of any good text book (short of the quickly 'n' violently thing)?

One point I'd like to add: If you can assume that 'dog' is going to be a Dog object, the KISS thing to do would simply be to make the method param type Dog (or better, interface IBarkable), instead of object. Take all ambiguity out of the equation altogether :).

Do you read a lot of Fowler's stuff? I read a lot of his stuff on enterprise architecture, and he really is quite a wise man, if a bit of a purist (not necessarily a bad thing)

Majesticmerc, on 19 August 2012 - 22:10, said:

One point I'd like to add: If you can assume that 'dog' is going to be a Dog object, the KISS thing to do would simply be to make the method param type Dog (or better, interface IBarkable), instead of object. Take all ambiguity out of the equation altogether :).
The example is kinda forced, but the one I actually see most of the time is something like:

IFoo a = GetService(typeof(IFoo)) as IFoo;
a.Foo();

Using "as" here makes no sense because unless there's something terribly wrong with GetService, it should return the type you've just asked for. And anyway the next line demonstrates you're expecting the cast to succeed anyway, so again I'd rather get an InvalidCastException immediately than a NullReferenceException some arbitrary number of lines later.

Quote

Do you read a lot of Fowler's stuff? I read a lot of his stuff on enterprise architecture, and he really is quite a wise man, if a bit of a purist (not necessarily a bad thing)
No but I guess I should. :p

Dr_Asik, on 19 August 2012 - 22:24, said:

No but I guess I should. :p

In all fairness, I only read some of his stuff because it was relevant to my line of work at the time (domain driven design, related design patterns, etc), but I wouldn't consider him a guru exactly. He's a big cheese in Agile methodology circles though from what I gather.
I read your post a couple days ago.
Now I opened EnumerableCollectionView.cs in the BCL source (the 4.5 one was released the other day) to look something up...and what do I see in the constructor?

_view = new ListCollectionView(_snapshot);

INotifyCollectionChanged incc = _view as INotifyCollectionChanged;
incc.CollectionChanged += new NotifyCollectionChangedEventHandler(_OnViewChanged);

INotifyPropertyChanged ipc = _view as INotifyPropertyChanged;
ipc.PropertyChanged += new PropertyChangedEventHandler(_OnPropertyChanged);
Looks like some MS devs could benefit from reading this :p

(EDIT: I read the whole source of the file...it makes this code look good)

Aethec, on 21 August 2012 - 23:25, said:

I read your post a couple days ago.Now I opened EnumerableCollectionView.cs in the BCL source (the 4.5 one was released the other day) to look something up...and what do I see in the constructor?
_view = new ListCollectionView(_snapshot);INotifyCollectionChanged incc = _view as INotifyCollectionChanged;incc.CollectionChanged += new NotifyCollectionChangedEventHandler(_OnViewChanged);INotifyPropertyChanged ipc = _view as INotifyPropertyChanged;ipc.PropertyChanged += new PropertyChangedEventHandler(_OnPropertyChanged);
Looks like some MS devs could benefit from reading this :p(EDIT: I read the whole source of the file...it makes this code look good)
That's amazing. Could you provide a link to said source?
Here's a pastebin: http://pastebin.com/bqa7m1Pe

Or, if you downloaded the .NET 4.5 Reference Source here : http://referencesour...tframework.aspx it's in Source\.NET 4.5\4.5.50709.0\net\wpf\src\Framework\MS\Internal\Data\EnumerableCollectionView.cs\550320.
Improper use of casts AND no type inference? Microsoft must be trolling you. :p
I don't get it. They cast for the explicit interface methods, but that doesn't require "as". It looks to me like an error although I find it hard to believe.

Dr_Asik, on 23 August 2012 - 00:59, said:

I don't get it. They cast for the explicit interface methods, but that doesn't require "as". It looks to me like an error although I find it hard to believe.
It is an error. Look at the rest of the file; that doesn't look like it was done by good programmers.

For starters, they should use weak events...

+SOOPRcow
Sep 01 2012 21:52
You know what drives me nuts...

var myValue = null as string;

I am so not even joking, it was all done just so they could use var.
what the...i don't even...
One thing I noticed in PackageStore.cs was this code, which I found intriguing:

(I only copied the relevant code)
...
// the "sealedness" of ZipPackage. Checking the object type is more reliable way
// than using "as" or "is" operator.
if (package != null && package.GetType() != typeof(ZipPackage))
{
....

location of source is: ..\Source\.NET 4.5\4.5.50709.0\net\wpf\src\Core\CSharp\System\IO\Packaging\PackageStore.cs\550320

Anyway, so no "as" or "is" in this case.

=-Chris

May 2013

S M T W T F S
   1234
567891011
12131415161718
19 202122232425
262728293031 

Categories