6

Possible Duplicate:
Casting: (NewType) vs. Object as NewType

In C#, why ever cast reference types when you can use "as"?

Casting can generate exceptions whereas "as" will evaulate to null if the casting fails.

Wouldn't "as" be easier to use with reference types in all cases?

eg:

MyObject as DataGridView

rather than,

(DataGridView)MyObject
Community
  • 1
  • 1
CJ7
  • 22,579
  • 65
  • 193
  • 321
  • @Jarrett Meyer - that question asks what the difference is. This question is different. Knowing the difference, it asks why a cast that throws is better than a cast that doesn't throw. – Daniel Earwicker Aug 13 '10 at 10:11

8 Answers8

9

Consider the following alternatives:

Foo(someObj as SomeClass);

and:

Foo((SomeClass)someObj);

Due to someObj being of the wrong type, the first version passes null to Foo. Some time later, this results in a NullReferenceException being thrown. How much later? Depends on what Foo does. It might store the null in a field, and then minutes later it's accessed by some code that expects it to be non-null.

But with the second version, you find the problem immediately.

Why make it harder to fix bugs?

Update

The OP asked in a comment: isn't is easier to use as and then check for null in an if statement?

If the null is unexpected and is evidence of a bug in the caller, you could say:

SomeClass c = someObj as SomeClass;
if (c == null)
{
    // hmm...
}

What do you do in that if-block? There are two general solutions. One is to throw an exception, so it is the caller's responsibility to deal with their mistake. In which case it is definitely simpler to write:

SomeClass c = (SomeClass)someObj;

It simply saves you writing the if/throw logic by hand.

There is another alternative though. If you have a "stock" implementation of SomeClass that you are happy to use where nothing better is available (maybe it has methods that do nothing, or return "empty" values, etc.) then you could do this:

SomeClass c = (someObj as SomeClass) ?? _stockImpl;

This will ensure that c is never null. But is that really better? What if the caller has a bug; don't you want to help find bugs? By swapping in a default object, you disguise the bug. That sounds like an attractive idea until you waste a week of your life trying to track down a bug.

(In a way this mimics the behaviour of Objective-C, in which any attempt to use a null reference will never throw; it just silently does nothing.)

Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284
  • +1 I keep seeing `Foo(someObj as SomeClass);` in code (without a `null` check, that is). It baffles me as to why people do this. Is there a book somewhere that says `(SomeClass)` are bad? – Tim Robinson Aug 13 '10 at 10:11
  • Isn't it easier to check whether the variable is null after the "as" statement, rather than dealing with the an exception? – CJ7 Aug 13 '10 at 10:14
  • Can you do anything useful when `someObj` is `null`? If not -- if your code can assume that `someObj` is in fact `SomeClass` -- then what's the point? – Tim Robinson Aug 13 '10 at 10:15
  • But what if you didn't write `Foo`, and I did? How do I know, upon recieving a null program argument if you meant it to be null or not? Maybe my code is meant to be able to accept null arguments, but you thought you were passing an argument that was non-null and of an okay class. If you used a cast, you'd be told immediately. If you used an `as`, you don't get to know until you try and do something later and get a result you don't expect (which might not even be an exception!) – Stephen Aug 13 '10 at 10:18
  • @Tim Robinson - if the reference is allowed to be null, then yes, you would of course want to use `as`. Do I need to update the answer to clarify that? – Daniel Earwicker Aug 13 '10 at 10:20
  • Useful rule for nulls: treat them as poisonous. Reject them in your inputs. Avoid returning them as outputs. – Tim Robinson Aug 13 '10 at 10:21
  • @Daniel - that comment was for @Craig – Tim Robinson Aug 13 '10 at 10:21
  • 1
    @Tim: folks coming from C++ learn that `()` casts are bad, and transfer this to C# despite the very different cast semantics. – Anton Tykhyy Aug 13 '10 at 10:39
  • @Tim, sometimes it's reasonable. Common example is if you've implemented `IEquatable.Equals()` and it includes the null-check it should include, then the appropriate override for `Object.Equals()` is almost always `return Equals(obj as MyType)`, as the result of passing null and a different type *should* be the same in this case. – Jon Hanna Aug 13 '10 at 10:56
  • @Jon Hanna - that's an example of a situation where the target reference is allowed to be `null`, as the "contract" of `Object.Equals` is supposed to handle it by design. The point here is not to deny the existence of those situations, but that they are comparatively rare, and so it is usually better to stop `null`s from finding their way into data structures as soon as possible. – Daniel Earwicker Aug 13 '10 at 11:08
  • Really the underlying problem here is that the language has no support for non-nullable references, despite the fact that the overwhelming majority of references in real programs never need to store the value `null`. http://qconlondon.com/london-2009/presentation/Null+References:+The+Billion+Dollar+Mistake – Daniel Earwicker Aug 13 '10 at 11:09
  • @DanielEarwicker: I didn't see the presentation, but I'm curious: how can non-nullable reference types work in a language which does not force every array slot or class field to be written before any can be read? If an algorithm will when working properly write every array element before that particular element is read, having a failure to initialize an element cause a null which triggers an exception seems less likely to mask a bug than would requiring that all array elements be pre-written with some "default" value. – supercat Nov 08 '13 at 20:34
  • @supercat - the idea is that non-nullability would be an *optional* attribute of a reference variable, not that it would become mandatory for all reference variables. Consequently an ordinary array would still be an ordinary array. And it would not be essential for an extended language to support non-nullable array element types, see: http://research.microsoft.com/en-us/projects/specsharp/ - an array could still be used as an internal implementation detail of some container of non-nullables. – Daniel Earwicker Nov 11 '13 at 12:19
  • @DanielEarwicker: I can see that there could be some benefit to being able to declare that a field or array slot should never be read when it is null, and that if the compiler can't statically confirm that it won't be it should generate code to throw an exception if it is [I'd like an assignment from such an array or field to a non-nullable variable to not require a cast]. I guess I don't see `null` as a huge problem, though, since even if null-related mistakes don't get flagged at the source, they do generally get flagged pretty quickly. – supercat Nov 11 '13 at 16:23
  • @DanielEarwicker: I can see that such types could be a benefit in .NET, but much more useful would be variations on "const ref". Having multiple storage location types for each class type, so that a "list whose items encapsulate the identities of things of type Fnord" would be a different type from a "list whose items encapsulate the mutable properties of things of type Fnord", as opposed to both things being a `List`, would have been highly beneficial as well, but retrofitting that into the Framework would likely be harder. – supercat Nov 11 '13 at 16:33
2

operator 'as' work with reference types only.

Arseny
  • 7,251
  • 4
  • 37
  • 52
  • The question has been updated to clarify that it is about reference types only (as it talks about `null` I thought that was pretty clear already). – Daniel Earwicker Aug 13 '10 at 10:10
2

Sometimes, you want the exception to be thrown. Sometimes, you want to try to convert and nulls are OK. As already stated, as will not work with value types.

Jarrett Meyer
  • 19,333
  • 6
  • 58
  • 52
  • Damn, y'beat me to it ;) exactly, sometimes you want an exception to be thrown, which is perfectly valid. +1 – Kieran Senior Aug 13 '10 at 09:59
  • Why would you want the exception to be thrown? – CJ7 Aug 13 '10 at 10:06
  • Because the calling code has done something that doesn't make sense and the only sensible response is to throw an exception. It's no different to the way that if you called Load() on an XMLDocument with content that wasn't XML then you want it to throw an exception. Not doing so just leaves you with an object that cannot be sensibly used, but which you don't know cannot be sensibly used. – Jon Hanna Aug 13 '10 at 10:11
1

If, when you write the code to make the cast, you are sure that the cast should work, you should use (DataGridView)MyObject. This way, if the cast fails in the future, your assumption about the type of MyObject will cause an invalid cast exception at the point where you make the cast, instead of a null reference exception at some point later.

If you do want to handle the case where MyObject is not a DataGridView, then use as, and presumably check for it being null before doing anything with it.

tl;dr If your code assumes something, and that assumption is wrong at run-time, the code should throw an exception.

Douglas
  • 36,802
  • 9
  • 76
  • 89
1

One definite reason is that the object is, or could be (when writing a generic method, you may not know at coding-time) being cast to a value type, in which case as isn't allowed.

One more dubious reason is that you already know that the object is of the type in question. Just how dubious depends on how you already know that. In the following case:

if(obj is MyType)
  DoMyTypeStuff((MyType)obj);
else
  DoMoreGeneralStuff(obj);

It's hard to justify using as here, as the only thing it really does is add a redundant check (maybe it'll be optimised away, maybe it won't). At the other extreme, if you are half-way to a trance state with the amount of information you've got in you're brain's paged-in memory and on the basis of that you are pretty sure that the object must be of the type in question, maybe it's better to add in the check.

Another good reason is that the difference between being of the wrong type and being null gets hidden by as. If it's reasonable to be passing in a string to a given method, including a null string, but it's not reasonable to pass in an int, then val as string has just made the incorrect usage look like a completely different correct usage, and you've just made the bug harder to find and potentially more damaging.

Finally, maybe if you don't know the type of the object, the calling code should. If the calling code has called yours incorrectly, they should receive an exception. To either allow the InvalidCastException to pass back, or to catch it and throw an InvalidArgument exception or similar is a reasonable and clear means of doing so.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
0

From MSDN (as (C# reference)):

the as operator only performs reference conversions and boxing conversions. The as operator cannot perform other conversions, such as user-defined conversions, which should instead be performed using cast expressions.

Oded
  • 489,969
  • 99
  • 883
  • 1,009
0

Taking into consideration all of the comments, we came across this just the other day and wondered why you would do a direct cast over using the keyword as. What if you want the cast to fail? This is sometimes the desirable effect you want from a cast if you're casting from a null object. You then push the exception up the call stack.

So, if you want something to fail, use a direct cast, if you're okay with it not failing, use the as keyword.

Kieran Senior
  • 17,960
  • 26
  • 94
  • 138
  • Why would you want the cast to fail? Besides, if the cast fails using "as" you will be given null. – CJ7 Aug 13 '10 at 10:05
  • Some software environments cater for generic types, like ours does, in order to increase the flexibility. New types can be introduced ad-hoc. For example, we use dynamic casters that can convert from one type to another even though they're not related. This is done so the end-type is "user-friendly" (perhaps immutable, or with hidden fields). If the cast doesn't work, we need to know about it! – Kieran Senior Aug 13 '10 at 11:00
0

As is faster and doesn't throw exceptions. Therefore it is generally preferred. Reasons to use casts include:

Using as, you can only assign types that are lower in the inheritance tree to ones that are higher. For example:

object o = "abc" as object;
DataGridView d = "abc" as DataGridView // doesn't do anything

DataGridView could create a custom cast that does allow this. Casts are defined on the target type and therefore allow everything, as long as it's defined.

Another problem with as is that it doesn't always work. Consider this method:

IEnumerable<T> GetList<T>(T item)
{
  (from ... select) as IEnumerable<T>
}

This code fails because T could also be a Value Type. You can't use as on those because they can never be null. This means you'll have to put a constraint on T, while it is actually unnecesary. If you don't know whether you're going to have a reference type or not, you can never use as.

Of course, you should always check for null when you use the as keyword. Don't assume no exceptions will be thrown just becase the keyword doesn't throw any. Don't put a Try {} Catch(NullReferenceException){} around it, that't unneccesary and bloat. Just assign the value to a variable and check for null before you use it. Never use it inline in a method call.

Jouke van der Maas
  • 4,117
  • 2
  • 28
  • 35