Representation and Identity

Representation and Identity

Rate This
  • Comments 26

(Note: not to be confused with Inheritance and Representation.)

I get a fair number of questions about the C# cast operator. The most frequent question I get is:

short sss = 123;
object ooo = sss;            // Box the short.
int iii = (int) sss;         // Perfectly legal.
int jjj = (int) (short) ooo; // Perfectly legal
int kkk = (int) ooo;         // Invalid cast exception?! Why?

Why? Because a boxed T can only be unboxed to T. (*) Once it is unboxed, it’s just a value that can be cast as usual, so the double cast works just fine.

Many people find this restriction grating; they expect to be able to cast a boxed thing to anything that the unboxed thing could have been cast to. There are ways to do that, as we’ll see, but there are good reasons why the cast operator does what it does.

To understand why this design works this way it’s necessary to first wrap your head around the contradiction that is the cast operator. There are two (¤) basic usages of the cast operator in C#:

  • My code has an expression of type B, but I happen to have more information than the compiler does. I claim to know for certain that at runtime, this object of type B will actually always be of derived type D. I will inform the compiler of this claim by inserting a cast to D on the expression. Since the compiler probably cannot verify my claim, the compiler might ensure its veracity by inserting a run-time check at the point where I make the claim. If my claim turns out to be inaccurate, the CLR will throw an exception.
  • I have an expression of some type T which I know for certain is not of type U. However, I have a well-known way of associating some or all values of T with an “equivalent” value of U. I will instruct the compiler to generate code that implements this operation by inserting a cast to U. (And if at runtime there turns out to be no equivalent value of U for the particular T I’ve got, again we throw an exception.)

The attentive reader will have noticed that these are opposites. A neat trick, to have an operator which means two contradictory things, don’t you think?

This dichotomy motivates yet another classification scheme for conversions (†).  We can divide conversions into representation-preserving conversions (B to D) and representation-changing conversions (T to U). (‡) We can think of representation-preserving conversions on reference types as those conversions which preserve the identity of the object. When you cast a B to a D, you’re not doing anything to the existing object; you’re merely verifying that it is actually the type you say it is, and moving on. The identity of the object and the bits which represent the reference stay the same. But when you cast an int to a double, the resulting bits are very different.

All the built-in reference conversions are identity-preserving (£). Obviously trivial “conversions” such as converting from int to int are also representation-preserving conversions. All user-defined conversions (§) and non-trivial value type conversions (such as converting from int to double) are representation-changing conversions. Boxing and unboxing conversions are all representation-changing conversions.

The representation-preserving conversions that are known to never fail often result in no codegen at all (₪). If a representation-preserving conversion could fail then a castclass instruction is emitted, which does a runtime check and throws if the check fails.

But each representation-changing conversion is handled in its own special way. User-defined conversions are resolved using a special version of the overload resolution algorithm, and generated as a call to the appropriate static method. Boxing and unboxing conversions are generated as box and unbox instructions. All the other built-in conversions (int to double, and so on) are generated as custom sequences of instructions that do the right conversion.

So now that you know that, consider what the compiler would have to do to make this work the way some people expect:

int kkk = (int) ooo;

All that the compiler knows is that ooo is of type object. It could be anything. Suppose it is a boxed int – then the compiler should generate an unboxing instruction. Suppose it is a boxed short. Then the compiler should unbox the short and then generate the custom sequence of instructions that convert a short to an int. Suppose it is a boxed double – same thing, but different instructions. And so on, for all the built-in conversions that go to integer.

This would be a huge amount of code to generate, and it would be very slow. The code is of course so large that you would want to put it in its own method and just generate a call to it. Rather than do that by default, and always generate code that is slow, large and fragile, instead we’ve decided that unboxing can only unbox to the exact type. If you want to call the slow method that does all that goo, it’s available – you can always call Convert.ToInt32, which does all that analysis at runtime for you. We give you the choice between “fast and precise” or “slow and lax”, and the sensible default is the former. If you want the latter then call the method.

That’s just the built-in conversions. Let’s continue imagining what would have to happen if we wanted all possible conversions to int to just work out correctly at runtime, instead of just bailing out early if the boxed thing is not an int.

Suppose the object is a Foo where there is a user-defined conversion from Foo (or one of its base classes) to int (or a type that int is explicitly convertible from, like, say, Nullable<int>). Then the compiler would need to generate a call to that conversion method, just as it would if the type had been known at compile time, and then possibly also generate the conversion from the return type of the method to int.

Remember, there could be arbitrarily many such conversion methods on arbitrarily many types. The type Foo and its conversion method might not even be defined in the assembly currently being compiled or any assembly referenced. Therefore the compiler would have to generate code to interrogate Foo at runtime, do the overload resolution analysis, and then dynamically spit the code to do the call.

Which is exactly what the compiler does in C# 4.0 if the argument to the cast is of type “dynamic” instead of object. The compiler actually generates code which starts a mini version of the compiler up again at runtime, does all that analysis, and spits fresh code. This is not fast, but it is accurate, if that’s what you really need. (And the spit code is then cached so that the next time this call site is hit, it is much faster.)

I don’t think people really expect the compiler to start up again at runtime every time they cast an object to int; I think they just haven’t thought through carefully exactly how much analysis solving the problem would take. Rather a lot, it turns out.

*************

(*) Or Nullable<T>.

(¤) There are others that are not germane to this discussion. For example, a third usage is “Everyone knows that this D is also of base type B; I want the compiler to treat this expression of type D as a B for overload resolution purposes.” That would clearly be an identity-preserving conversion.

(†) There are many ways to classify conversions; we already divide conversions into implicit/explicit, built-in/user-defined, and so on. For the purposes of this discussion we’ll gloss over the details of those other classifications.

(‡) I’m glossing over here that certain conversions that the C# compiler thinks of as representation-changing are actually seen by the CLR verifier as representation-preserving. For example, the conversion from int to uint is seen by the CLR as representation-preserving because the 32 bits of a signed integer can be reinterpreted as an unsigned integer without changing the bits. These cases can be subtle and complex, and often have an impact on covariance-related issues; see next footnote.

I’m also ignoring conversions involving generic type parameters which are not known at compile time to be reference or value types. There are special rules for classifying those which would be major digressions to get into.

(£) This is why covariant and contravariant conversions of interface and delegate types require that all varying type arguments be of reference types. To ensure that a variant reference conversion is always identity-preserving, all of the conversions involving type arguments must also be identity-preserving. The easiest way to ensure that all the non-trivial conversions on type arguments are identity-preserving is to restrict them to be reference conversions.

(§) The rules of C# prohibit all user-defined conversions that could possibly be identity-preserving coercions. More generally, all user-defined conversions that could possibly be any "standard" conversion are illegal.

(₪) Again, I’m ignoring irksome generic issues here. There are situations where humans can prove mathematically that two generic type parameters must be identical at runtime, but the verifier is not smart enough to make those same deductions and requires the compiler to emit type checks.

  • "VB allows you to say, 'I don't care how you do it, don't bother me with the details, just get it done.'" (commongenius).

    Indeed we call that declarative programming. This is a quality of markup languages, SQL, RegularExpressions and highly desirable in many cases.

    This is not to say that it's always desirable indeed I appreciate the recognition of a valid though different philosophy. I've recently come to terms with CType - until recently I was more fond of DirectCast because of its explicitness. I also sort of liked that the VB implementation of CType vs DirectCast separated out the two functions of the C# cast syntax - i.e. Conversion (representation-changing) versus Casting (identity-preserving). I've since decided that I was just being needlessly obsessive and that differing to the tool to exercise the most appropriate (hopefully optimized path) method was maintenence-wise more concise (or consistent).

    Ultimately it's a choice between who gets the burden for optimizing implementation - the SQL Server team has to be deligent when emitting query plans to use all information to produce the query plan that's most efficient whereas with the more explicit - imperative style the burden is shifted to the user. I prefer the former but always appreciate the option to go explicit as required but I think it's most important for the programmer to be aware that in these cases they are delegating responsibility to the tool - that it's not just magic.

    "VB is a 'do what I mean' language, C# is a 'do what I say' language " (Eric).

    I like this summarization. I rings well with my discoveries about some of the finer rules of Option Strict Off in VB (which is an advanced language feature, btw, not a novice one - which is why it should be off by default). A lot of people characterize it as a feature that let's you just willy nilly do anything and everything and the compiler will just make things happen by parsing horoscopes. This is not true. When you look at the rules it won't let you do anything that you couldn't say explicitly (and can still give compile time warnings for things it knows you definitely can't do) and even being explicit about the cast/conversion it's still just as possible to ask to do something that will fail (I would argue that having to type CType doesn't actually make me more likely to realize a conversion will fail at runtime but then again I don't do a lot of conversions anyway)

    It's a philosophical difference of whether I have to say Dim i As Integer = CType(o, Integer) or whether it's sensible for the tool to infer from my assining o to i that I would naturally want - need - mean - to convert o to an Integer first - whether I should have to say something that I'd have to say anyway.

    Good read.

  • You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • I think that all went straight over my head ... dam I feel stupid now, time to get those mcts books out.

  • I'm a new fan of your series.  Keep these fantastic articles coming!

    One correction:  Nullable<ValueType> is not imiplicitly convertable to ValueType.  It's the other way around.

  • Sorry for the repost, wanted to provide a sample.  If you can merge these two posts, please do.

    int i = 0;

    int? j = null;

    int i = j;    // won't compile

    int i = (int)j;    // throws InvalidOperationException

    int i = j ?? 0;    // works

  • Hello Eric,

    I red your nice article.

    I don't know if it is because I missed somthing important but I feel that C# could support something like that:

           // ******************************************************************
           public class Something       {       }
           // ******************************************************************
           public class SomethingWrapper
           {
               private Something _something;
               public static implicit operator Something(SomethingWrapper wrapper)
               {
                   return wrapper.GetSomething();
               }
               public Something GetSomething()
               {
                   return _something;
               }
               public  SomethingWrapper(Something something)
               {
                   _something = something;
               }
               // More code here...
           }
           // ******************************************************************
           private void button1_Click(object sender, RoutedEventArgs e)
           {
               SomethingWrapper sw = new SomethingWrapper(new Something());
               Object o = sw;
               Something s = (Something)o; // InvalidCastException.

    If it exists only one way where the compiler could pass from "Object"(real object: SomethingWrapper) to "Something" object,

    then why the compiler does not create the code and just do it ?

    Why it would need dynamic type for that ?

    Why not doing it on object ?

    In order to make your scenario work, the generated code has to start the C# compiler, run overload resolution to determine that there is a conversion from the runtime type of the object, somehow call the conversion, and return the result. Remember, we have to run this code on EVERY cast. That would make C# a very slow language indeed. Now, if you want to start up the compiler again at runtime, in C# 4 you can do that if you choose to take that performance hit. Make the argument dynamic, and we'll do all the analysis at runtime. -- Eric

    If it would be supported, it would open the door to a fantastic world of generic wrapper classes.

    SomethingWrapper could be generics to works on anything, not only Something.

    Am I wrong ? Did I missed something somewhere ?

    Thanks

  • Thanks Eric for your feedback, it was very quick !

    I absolutely do not want to use "dynamic" typed object. It's untyped (no intellisense and many more, you know all that...). By definition is bad to use it!

    Althought the compiler is able to do portion of the discovery on the fly, actually (it could change), I do not want to touche dynamic object with a 10' pole.

    Related to my previous sample...

    I would probably be able to do:

    something s = (dynamic)o; // Pass throught dynamic object to do dynamic cast to typed type ?

    Does the language will offer a new keyword of the style "dynamic_cast"... Will I be able to write:

    c#3.5 : Something s = (Something)o; // InvalidCastException.

    c#4   : Something s = dynamic_cast<Something>o; // Find the path to the object, throw an exception if more than one path available (no dynamic objec)

    That dynamic_cast is very nice... Hope Anders thought about it for C#4 !!!

  • In fact, I do not understand why cast could not cast its source to its real underlying object type source before trying to cast ?

    Why I can't do that, and why it would cost a lot to do it ?

           public class EnumValueWrapper<T>

           {

               // **************************************************************

               private readonly T _enumValue;

               // **************************************************************

               public EnumValueWrapper(T enumValue)

               {

                   _enumValue = enumValue;

               }

               // **************************************************************

               public static implicit operator T(EnumValueWrapper<T> enumValue)

               {

                   return enumValue.GetEnumValue();

               }

               // **************************************************************

               public T GetEnumValue()

               {

                   return _enumValue;

               }

       ... // Elided for clarity

           }

    ...

    public enum FirstLast { first, last };

    FirstLast firstLast;

    EnumValueWrapper<FirstLast> firstLatsWrapper = new EnumValueWrapper<FirstLast>(firstLast);

    Object objFirstLast = firstLatsWrapper;

    firstLast = (FirstLast)objFirstLast; // Exception

    The compiler already as to do a check at runtime to ensure inheritance compatibility. There is already a kind of performance penalty.

    It could pregenerate code like (pre IL):

    firstLast = (FirstLast)objFirstLast;      ==>     firstLast = (FirstLast)(objFirstLast.GetType())objFirstLast;

    Everything would work perfectly and the cost would be minimal (I think). Are you agree ?

  • Hi Eric, I believe you made a mistake in this post (or I misunderstood it).

    “Suppose the object is a Foo where there is a user-defined conversion from Foo (or one of its base classes) to int (or a type that int is implicitly convertible to, like, say, Nullable<int>).”

    If a user-defined conversion from Foo to Nullable<int> exists, I don’t see what relevance it has that int is implicitly convertible to Nullable<int> if you’re trying to cast to int. It seems only relevant to me that Nullable<int> is convertible to int, and only *explicitly*, so it seems that you might have meant “... or a type that is explicitly convertible to int”, in which case Nullable<int> would still be a candidate, but that would mean you made two mistakes in one sentence, which seems unlikely ;-).

    If I am wrong, would you like to clarify where my error lies?

    It is a confusing paragraph, sorry. Let me clarify. Suppose you have a cast expression that casts a Foo to int. Suppose further there is an implicit or explicit user-defined conversion from Foo to Nullable<int>.  When there is a cast operator in the code the compiler is permitted to put up to one *built-in* conversion, either explicit or implicit, on either side of an implicit or explicit user-defined conversion.  That is "(int)foo" is allowed to be bound as "(int)(int?)foo". There's an explicit or implicit conversion from foo to int? and an explicit conversion from int? to int. You are right that it would have been more clear to phrase it the other way. I'll change it. - Eric

  • Hello Eric,

    This is the first post I see, but I can say this is a really good post.

    Because of this post, I'll now see the blogs more often.

    It also answered some of my questions.

    Thank you

  • Hi,

    What do you think about using one of the following simple functions

    static T Convert<T>(object value)

    {

        return (T)System.Convert.ChangeType(value, typeof(T));

    }

    static void Convert<T>(object value, out T t)

    {

        t = (T)System.Convert.ChangeType(value, typeof(T));

    }

    This is doing both unboxing and casting at once. I don't know performance of this, but this is handy. I especially like the second one since I don't even have to specify T:

    int kkk;

    Convert(ooo, out kkk); // Does the trick

Page 2 of 2 (26 items) 12