Nulls not missing anymore

Nulls not missing anymore

  • Comments 98

In the past, I have talked about how your feedback is a critical part of us building the right product.  Recently, we took a big DCR (Design Change Request) into Visual Studio 2005 that was in response to your feedback.  This was a hard call, because it was a big change that touched many components including the CLR.  Nonetheless, we decided to take this change at this late stage in the game because a) this was the right product design and I always believe in optimizing for the long-term and b) I had confidence in the team(s) to be able to get this work done in time for Visual Studio 2005.  This is a classic example of how we are listening to your feedback that results in a better product for all of us.

 

This particular work-item relates to the new System.Nullable data type included as part of the .Net Runtime in Visual Studio 2005.  If you want some context on what the Nullable type is all about, you can read this article in MSDN.

 

The August CTP for Visual Studio 2005 that is coming out in the next week or so will have this new implementation for the System.Nullable data type.

 

The C# team used the following example to illustrate why our original implementation could be confusing and how we changed support for Nullable type to make it more elegant, easy to understand and use for application developers.

 

We designed the Nullable type to be the platform solution, a single type that all applications can rely on to uniformly represent the null state for value types.  Languages like C# went ahead and built in further language features to make this new primitive feel even more at home.  The idea was to blur the subtle distinction between this new value-type null and the familiar reference-type null.  Yet, as it turns out, enough significant differences remained to cause quite a bit of confusion.

 

We soon realized the root of the problem sat in how we chose to define the Nullable type.  Generics were now available in the new runtime and it seemed quite simple to use this feature to build up a new parameterized type that could easily encode both a value type and an extra flag to describe its null state.  And by defining the Nullable type also as a value type we retained both the runtime behaviors and most of the performance of the underlying primitive. No need to special case anything in the runtime.  We could handle it all as just an addition to the runtime libraries, or so we thought.

 

As several of you pointed out, the Nullable type worked well only in strongly-typed scenarios.  Once an instance of the type was boxed (by casting to the base ‘Object’ type), it became a boxed value type, and no matter what its original ‘null’ state claimed, the boxed value-type was never null. 

 

      int? x = null;

      object y = x;

      if (y == null) {  // oops, it is not null?

        ...

      }

 

It also became increasingly difficult to tell whether a variable used in a generic type or method was ever null.

 

    void Foo<T>(T t) {

       if (t == null) {  // never true if T is a Nullable<S>?

       }

    }

 

Clearly this had to change.  We had a solution in Visual Studio 2005 Beta2 that gave users static methods that could determine the correct null-ness for nullable types in these more or less ‘untyped’ scenarios.  However, these methods were costly to call and difficult to remember to use.  The feedback you gave us was that you expected it to simply work right by default.

 

So we went back to the drawing board.  After looking at several different workarounds and options, it became clear to all that no amount of tweaking of the languages or framework code was ever going to get this type to work as expected.

 

The only viable solution was one that needed the runtime to change.  To do that, it would require concerted effort by a lot of different teams working under an already constrained schedule.  This was a big risk for us because so many components and products depend on the runtime that it has to be locked down much sooner than anything else.  Even a small change can have significant ripple effects throughout the company, adding work and causing delays.  Even the suggestion of a change caused quite a bit of turmoil.  Needless to say, many were against the proposal for very credible reasons.  It was a difficult decision to make. 

 

We were fortunate that so many here were willing to put in the extra work it took to explore the change, prototyping it and testing it, that a lot of the uncertainty and angst was put to rest, making the decision to go ahead all that much easier.

 

The outcome is that the Nullable type is now a new basic runtime intrinsic.  It is still declared as a generic value-type, yet the runtime treats it special.  One of the foremost changes is that boxing now honors the null state.  A Nullabe int now boxes to become not a boxed Nullable int but a boxed int (or a null reference as the null state may indicate.)  Likewise, it is now possible to unbox any kind of boxed value-type into its Nullable type equivalent. 

 

      int x = 10;

      object y = x; 

      int? z = (int?) y;  // unbox into a Nullable<int>

 

Together, these changes allow you to mix and match Nullable types with boxed types in a variety of loosely typed API’s such as reflection.  Each becomes an alternative, interchangeable representation of the other.

 

The C# language was then able to introduce additional behaviors that make the difference between the Nullable type and reference types even more seamless.  For example, since boxing now removes the Nullable wrapper, boxing instead the enclosed type, other kinds of coercions that also implied boxing became interesting.  It is now possible to coerce a Nullable type to an interface implemented by the enclosed type.

 

       int? x = 0;

       IComparable<int> ic = x;  // implicit coercion

 

I sincerely hope these changes were worth the effort and that application builders will find the definition of a common representation for null value types beneficial for the development of their products.  

 

Namaste!

 

Leave a Comment
  • Please add 1 and 7 and type the answer here:
  • Post
  • THANK YOU THANK YOU THANK YOU
  • One question - are method calls on nullable types seamless too in C#?

    For example is it possible to do this:

    DateTime? dt = DateTime.Now;
    Console.WriteLine(dt.ToShortDateString());

    (Even without this it's an AWESOME and incredible improvement - I'm only even mentioning it because this change too would be impossible to add in a later version if it's not done now, in case the value type being nulled has a Value or HasValue property of its own)
  • That's great to see the team listen to feedback and implement that at this stage of the product.
  • Lots happened with Nullable pretty late in the game and it sure was a big effort by a bunch of people...
  • Lots happened with Nullable pretty late in the game and it sure was a big effort by a bunch of people...
  • Despite the investment of time and money Microsoft has made in the name of community, there are those...
  • Looks nice, but does the following compile and work as intended?

    object x = null;
    int? y = (int?) x;
    if (y == null) {
    Console.WriteLine("It works as intended.");
    }
  • Sounds good. Somathat similar to ideas I've proposed 8 months ago at http://lab.msdn.com/productfeedback/viewfeedback.aspx?feedbackid=FDBK19417

    But will this print true at some time ?

    int? a = null;
    int? b = null;
    Console.WriteLine(a >= null); // It now print False
    Console.WriteLine(a >= b); // Also print False
    (reported ~2 months ago at http://lab.msdn.com/productfeedback/viewfeedback.aspx?feedbackid=FDBK30893 )

  • Efter at en del har p&#229;peget en del designm&#230;ssige problemer med System.Nullable typer i .NET 2.0, har...
  • This is fantastic. Special boxing rules for nullable types is an excellent idea. I'm impressed that so much energy is put into making the framework homogeneous throughout - makes my life much easier. Thanks a bunch.

  • To answer the question

    object x = null;
    int? y = (int?) x;
    if (y == null) {
    Console.WriteLine("It works as intended.");
    }

    does print 'It works as intended'

    x starts out as null, when it is unboxed, it becomes a int? with no value, and when y is compared against null, the C# compiler understands this syntax to mean !y.HasValue which makes the body of the if execute.

    For the example

    int? a = null;
    int? b = null;
    Console.WriteLine(a >= null);
    Console.WriteLine(a >= b);

    The first Writeline statement is now a compile time error. The second Writeline still produces false. I know that Erik believed it should be true since a == b, and the rational goes that a >=b === (a > b || a == b). The bottom line is that there is a design conflict because we would also like ot have a RelOp b is false if either a or b is nullable for any relational operator. In the end this latter property is deemed more imporant. I happen to agree with this.

    Finally the code

    DateTime? dt = DateTime.Now;
    Console.WriteLine(dt.ToShortDateString());

    does not compile even after this change (because the C# compiler does not auto-convert the DateTime? to a DateTime. Of course the following code does work.

    DateTime? dt = DateTime.Now;
    Console.WriteLine(dt.Value.ToShortDateString());











  • This is one of those tough changes that will pay dividends for a long time. Simply put the "right choice."

    Slightly off topic though how does this change affect the future of System.DBNull as a data type? Will it be deprecated in the near future? It does appear as though it's no longer needed.
  • Vance, thanks for the answers.

    The reason I belive that the dt.ToShortDateString() example should work is as follows.

    Currently the only members available on a Nullable type are Value, HasValue and the ones inherited from Object and ValueType.

    Value and HasValue are pointless to call directly because in both cases you can do the same thing more idiomatically - foo.Value is equivalent to "(T) foo" and foo.HasValue is equivalent to "foo != null". The members inherited from Object and ValueType are all guaranteed to be there on T anyway since T : struct, and if you're calling, eg, ToString on a Nullable<T> you probably expect to get T's ToString method anyway.

    So there's absolutely no reason for the C# compiler to provide direct access to the members of Nullable<T>.

    And, that being the case, you can just have the compiler interpret foo.Bar as foo.Value.Bar under the hood whenever foo is a Nullable and make life easier for everybody.

    (As I said, even without that this is a huge, fantastic change, but it would be even better if you took this one last step)
Page 1 of 7 (98 items) 12345»