What's the difference between "as" and "cast" operators?

What's the difference between "as" and "cast" operators?

Rate This
  • Comments 36

Most people will tell you that the difference between "(Alpha) bravo" and "bravo as Alpha" is that the former throws an exception if the conversion fails, whereas the latter returns null. Though this is correct, and this is the most obvious difference, it's not the only difference. There are pitfalls to watch out for here.

First off, since the result of the "as" operator can be null, the resulting type has to be one that takes a null value: either a reference type or a nullable value type. You cannot do "as int", that doesn't make any sense. If the argument isn't an int, then what should the return value be? The type of the "as" expression is always the named type so it needs to be a type that can take a null.

Second, the cast operator, as I've discussed before, is a strange beast. It means two contradictory things: "check to see if this object really is of this type, throw if it is not" and "this object is not of the given type; find me an equivalent value that belongs to the given type". The latter meaning of the cast operator is not shared by the "as" operator. If you say

short s = (short)123;
int? i = s as int?;

then you're out of luck. The "as" operator will not make the representation-changing conversions from short to nullable int like the cast operator would. Similarly, if you have class Alpha and unrelated class Bravo, with a user-defined conversion from Bravo to Alpha, then "(Alpha) bravo" will run the user-defined conversion, but "bravo as Alpha" will not. The "as" operator only considers reference, boxing and unboxing conversions.

And finally, of course the use cases of the two operators are superficially similar, but semantically quite different. A cast communicates to the reader "I am certain that this conversion is legal and I am willing to take a runtime exception if I'm wrong". The "as" operator communicates "I don't know if this conversion is legal or not; we're going to give it a try and see how it goes".

  • Very interesting post as always.

    I did some testing and it also appears the "as" operator handles one more special case: Converting from a ValueType to Nullable<T>.  (Or is this considered a boxing and unboxing conversion)  The code below worked for me:

    int i = 1;

    int? n = i as int?;

  • Ok since I'm not a C# programmer: Just curious where would you want to use the "as" operator instead of an explicit cast?

    I think I covered that in the article, but to sum up, here are two ways to think about it.

    First, "as" means "I expect that sometimes this will not succeed, give me null in those cases". A cast means "I expect that this will always succeed; if I am wrong, then please crash the program".

    Second, "as" means "I want to know what this object *really is*, not what it is convertible to by some representation-changing specially-defined conversion rule. A cast means "convert this thing using whatever crazy mechanism you need to do to make it work."

    Choose the one that matches the meaning you intend to implement. -- Eric

    I mean as we all know (thanks to this blog at least ;) ) things get implemented because many people want/need them and it's worth the cost of implementation, or is the as operator just a byproduct of something else?

     

  • @Voo... One case is when it MIGHT be of the type:

    class Animal;

    class Dog : Animal {}

    class Cat : Animal {}

    Animal a = new Dog();

    Cat c = a as Cat;

    One "anti-pattern" to watch out foo (I see it far too often) is:

    if (a is T)

    {

      T t = a as T;

    }

    or even

    if (a is T)

    {

      T t = (T) a;

    }

    BOTH are better performed by a single invocation of "as" and a null check.

  • Another anti-pattern that I often see is this:

     (x as T).Foo()

    Apparently people write it because they consider "as" to be more "aesthetically pleasing" than cast with its extra parentheses (and I can't blame that for it, to be honest - C cast syntax is one of the worst parts of the language, second only to pointer/array declarators), and ignore other implications, such as the intended semantics of the cast.

  • @TheCPUWizard...

    You are quite correct, but if you start with...

    if (o is SomeClass)

    {

        SomeClass sc = o as SomeClass;

        /* Use sc */

    }

    else if (o is SomeOtherClass)

    {

        SomeOtherClass soc = o as SomeOtherClass;

       /* Use soc */

    }

    else /* etc */

    To rewrite this using as instead doesn't look quite so tidy. Maybe you could put the as operation and assignment inside the IF condition and then check the value returned by the assignment operator, but that doesn't look so nice.

    Besides, I would hope that a good compiler would optimise is-followed-by-as into a single operation anyway. No idea if any do.

  • @Pavel Minaev [MSFT]

    Totally agree. I kinda wish C# could do (X castas Y) to be exactly equivilent to ((Y)X), but that would mean a new reserved word.

  • The 'as' operator seems to be one of the most abused operators at my workplace. When asked, most devs say they use it "because it looks better." What often results, however, is that improper casts aren't caught until they try to use the null reference later on in the code. Consider this example:

    var someObject = (object)new SomeType();

    <bunch of code>

    var A = someObject as ADifferentType;

    var B = (ADifferentType)someObject;

    <whole bunch of code>

    <whole bunch of code>

    <whole bunch of code>

    A.DoSomething();

    B.DoSomething();

    Assume here that ADifferentType does NOT derive from SomeType. In this example, the 'as' operator sets A to null because it's not the proper type. However you don't catch that until much later in the code (and in the extreme case, in an entirely different method), where you try to reference it. This makes debugging difficult. On the other hand, when we try to direct cast to variable B, we get an exception immediately and our bug is pinpointed on the spot.

    In the world of generics (and therefore mostly type safe code), I find the 'as' operator is almost never appropriate. I have a standing rule with my devs that if they find themselves tempted to use 'as', come down and see me because, except for a few rare circumstances, it almost always indicates an architectural problem.

  • @Bill.... As of 2010 Beta 1, the compiler (neither C# nor JIT) perofrms that type of operation, and these can be long running operations. Your code effectively is:

    if ((o as SomeClass) != null)

    {

       SomeClass sc = o as SomeClass;

    }

    Would you EVER code that directly???

    @Mike...If you consider the use of RTTI (Runtime Type Identification) based logic to be an "architectural problem", then I agree with your post. However I consider it (RTTI) to be avaluable tool.  If you are going to "restrict" the use of "as", then IMPO, to be consistant you must place the EXACT same restraints on "is".

  • @TheCPUWizard

    "As of 2010 Beta 1, the compiler (neither C# nor JIT) perofrms that type of operation,"

    (Assuming "that type of operation" is refering to compilers optimising is-then-as.)

    That's a pity I think. It seems a simple optimisation, but I've never written a compiler so I can't really say. The fact we're having this conversation at all would indicate its a common enough thing that programmers do.

    "Would you EVER code that directly???"

    No, I wouldn't. I'd use the is operator instead. It looks simple enough with one if operation on its own, but I'm thinking more of a long else-if chain, and we only get the if-condition to do our thing.

    What's the alternative to my sample code in my earlier comment? I guess this...

    SomeClass sc;

    SomeOtherClass soc;

    /* 20 more. */

    if ((sc = o as SomeClass) != null)

    {   /* Use sc */  }

    else if ((soc = o as SomeOtherClass) != null)

    {  /* Use soc. */  }

    /* 20 More. */

    How much CPU time are we saving with this version? (vs the time spent going "what the .... is going on here?" when some tries to review my code.)

    If this (below) were legal, or the code were in a tight loop, I might be persuded.

    if ((SomeClass sc = o as SomeClass) != null) { }

  • @Bill - compare the situation to a method call. Multiple calls to the same method in sequence do not (and should not) be automatically reduced into a single one.

    As far as your second sample. I would NEVER code a chain like that. I would calculate the type ONCE and do a switch against constant values.

    As far as the timing goes, I recently was debugging code similar to the following:

    foreach (var o in coll)

    {

      T t = o as T;

      if (t != null)

        t.Property = Value;

    }

    The "as" took 95% of the processing time (ie 20x the time taken to assign the property...

  • Not that you asked...

    I think of casts a mild code smell, much like comments. When I have one, I want to take a minute to ask whether there's another design that wouldn't need it, and whether that design is attractive in other ways.

    In C#, comments are easy to find, thanks to their unique and obvious syntax.

    If C# had chosen a syntax like C++'s dynamic_cast<> operator, it would be easy to find them, but it didn't. C# casts are not syntactically obvious, since parens are used for a lot of things.  At least 'is' and 'as' are easy to find, but casts are not, and it's too late to change the language to help.

    My wish (request?) is that the C# tools of the future make it easy to find casts, so I can give them that extra scrutiny.

  • Another blog I learned something from.  I always assumed that (Cast) versus as were identical save for the difference when a failure occurred (in the same way that you have int.Parse + int.TryParse).

    Mike: Well said.  I also see a lot of similar things.  

    It's especially insidious when people speculatively cast something and then hand the reference off to another method to deal with it.  You get a failure down the line and that failure is now potentially a long ways away from the site of the cast, plus it's a NullRef instead of an InvalidCastException.  Even more galling is the fact that tools like ReSharper tell you that this is the case, yet people still ignore the warnings.

    I do agree that casting in C# is particularly ugly, though.  

    ((Blah)otherblah).SomeMethod(); is much harder to read and type compared to (otherblah as Blah).SomeMethod();  

    If anything, the direct cast should've been easier to write instead of the speculative cast via "as".  Like Mike said, I see it abused quite a lot.  I also agree that casting often reeks of a design problem.  Sometimes it's unavoidable, but most of the time it's indicative of a problem in the design.  

  • So noone else here has written an object.Cast<T>() extension method? "foo.Cast<Bar>().Baz()" is pretty easy to read to me. I also use object.IfType<T>(Action<T>) instead of as, which I find easier to read as well, but then again, I also prefer "enumerable.Each(x => Console.WriteLine(x))", so I'm probably not the best person to ask here...

  • @Simon Buchan: Given that there's Enumerable.Cast<T> in LINQ, I don't think it's a good idea to create another extension method with the same name. It won't be clear what's going on in all situations.

  • How do the casting operators behave when they encounter a null reference?

Page 1 of 3 (36 items) 123