Yet Another Comment on the C# “Cast” ( ) and “as” Operators…

posted in: Language Features | 0

Time and again, the question comes up whether to prefer the cast ( ) or the as operator in C# to perform conversions to reference types. According to the C# language specification, the two operators behave differently in several respects:

C# Conversion Operator ( ) as
Name Cast operator as operator
C# 3.0 Specification section 7.6.6 7.9.11
Can convert to ValueType Yes No
Executes user-defined explicit conversions Yes No
Compile-time errors CS0030: Cannot convert type 'X' to 'Y'.
  • CS0030: Cannot convert type 'X' to 'Y'.
  • CS0077: The as operator must be used with a reference type or nullable type ('Y' is a non-nullable value type).
    Runtime behavior if not convertible Throws InvalidCastException: Unable to cast object of type 'X' to type 'Y'. Returns null

    Given the above comparison table, it follows that you must use the cast operator ( ) in the following cases:

    • to convert to ValueType:
      object o = 137; var i = (int)o;
    • to convert via a user-defined explicit operator overload defined in the source type. Note that the overload may return anything, depending on its implementation: The same instance, a new instance of the same type, a new instance of a completely non-related type, null, a ValueType or reference type...
      // Declared inside a class called Source:
      static explicit operator Target(Source s) {return new Target();}

    By contrast, you must use the as operator in the following cases:

    • to be 100% sure that that the result points either to the same instance, or null:
      var bar = foo as Bar;
    • to return null if a runtime conversion to the desired reference type is not possible, as in this construct:
      var bar = foo as Bar ?? new Bar();

    Furthermore, the as operator is a bit more tolerant with generics at compile time than the cast operator. That's because the as operator casts directly to a type of the same inheritance line:

    TDerived GetDerived<TBase, TDerived>(TBase b) where TDerived: class {
        // Produces compiler error CS0030: Cannot convert 
        // type 'TBase' to 'TDerived'.
        // return (TDerived)b;
    
        // This compiles...
        return b as TDerived;
    }

    In all other cases, from a technical point of view, the choice between cast and as is irrelevant. Therefore, the right decision depends on what you want to express most in the source code. As a matter of taste, I find the as operator easier to read, as it goes from left to right, which is the natural way of reading source code, whereas the cast ( ) operator forces the mind to jump back and forth. It also looks like old-fashioned C code.

    Some tools, such as JetBrains' ReSharper, produce a warning if one applies the as operator without checking for null within the same scope. Unfortunately, this kind of flow analysis does not work when the null-checking logic is defined in a separate external helper method. The warning can be turned off, but only on a per-user basis. It may also be turned off with a specific ReSharper comment surrounding the critical section, but this leads to ugly cluttered code. Thus, if your company uses ReSharper, it might be a pragmatic reason to prefer the cast ( ) operator over the as operator.

    In my opinion, it would be better if the C# as operator did not return null, but throw an InvalidCastException on invalid casts. Nothing of importance would be lost—you can always check for null with the is operator—but a lot of misunderstandings would be prevented. Interestingly, the VB.NET language has the DirectCast operator, which does exactly this. A similar behavior could be implemented in C# like this:

    public static T As<T>(this object o) where T: class{
        if (o == null) throw new NullReferenceException();
        if (!(o is T)) throw new InvalidCastException();
        return o as T;
    }

    Last not least, in F#, explicit or implicit conversion operators do not even exist. There is only the as operator...

    Comments are closed.