Even More Conversion Trivia, Part Two
Reader Barry Kelly came up with the solution I was thinking of for my trivia question yesterday. If the value in question is, say, a nullable integer which has no value then casting it explicitly to object results in the null object. Calling a virtual method on a null object results in an exception at runtime. However, using it implicitly as an object boxes up the nullable int as a valid ValueType, which overrides ToString. So that's the difference: the one without the cast produces an empty string, the one with the cast throws an exception.
Honourable mention to reader Bill Wagner, who points out that if the object implements an interface with a ToString method then there are two ToString slots, one from the base class and one from the interface. Casting to object can cause overload resolution to pick the base version over the interface version, which might otherwise be preferred. (This reminds me of another fun fact about C# overload resolution which I will blog about at a later date.)
As Barry Kelly points out, the CLR took a design change with Visual Studio 2005 Beta 2 to implement the special case that a nullable without a value becomes a null ValueType rather than a boxed ValueType when boxed. How then do we implement the desired behaviour, ie, that calling ToString without a cast creates a boxed ValueType regardless of whether the nullable has a value?
Fortunately the CLR implemented a special kind of virtual call, called the "constrained" virtual call. Without the ability to constrain the transformation of a value to a particular type when making a virtual call, certain operations become difficult to codegen. (In particular, it is hard to do virtual calls on type parameters of generic types, because you might not know whether the type parameter is going to be a value type or a reference type under construction.) If you take a look at the IL we generate for something like foo.ToString() on a nullable int, it looks like:
ldsflda valuetype [mscorlib]System.Nullable`1<int32> Test::foo
constrained. valuetype [mscorlib]System.Nullable`1<int32>
callvirt instance string [mscorlib]System.Object::ToString()
Which tells the CLR to box up that thing as is, rather than doing a boxing operation. Had we inserted a cast in there we would have generated
ldsfld valuetype [mscorlib]System.Nullable`1<int32> Test::foo
box valuetype [mscorlib]System.Nullable`1<int32>
callvirt instance string [mscorlib]System.Object::ToString()
instead.
Comments
Anonymous
May 01, 2007
It's great that you went to so much trouble to get the wrong behavior! string x = null; x.ToString(); // NRE int? y = null; y.ToString(); // just fine! (This is a several years old rant, sorry. The nullable DCR made things much much MUCH better than they were before but this is one of its big failures...) The problem is with the mindset that keeps wanting to shove down developers' throats the fact that Nullable<T> is really a value type with its own set of properties and methods and the good old confusing value type behavior. When a developer writes "int?" they don't mean "I want a struct that might or might not hold an int". With few exceptions, the average C# developer doesn't know what a struct is. They mean "I want an int that might be null". And if it's null it should jolly well behave like other things behave when they're null! And if it isn't null it should jolly well behave like an int! I know it's too late to get these things fixed now. I know that we'll forever be stuck with instances of int? having .HasValue and .Value properties that are entirely redundant with built-in language facilities ("!= null" and "(int)") but block ever being able to call the methods on the underlying type directly on the instance of nullable. But just cos I know it doesn't mean I like it :(Anonymous
May 06, 2007
The biggest news in the C# community is the official announcement of Silverlight at the MIX conference.