Jason Crease

Test Engineer - Red Gate Software

Nullable Structs - An interesting 'Gotcha'

Published Wednesday, November 26, 2008 5:11 PM

One of the interesting new features in C# 2.0 was nullable valuetypes.  Using these, you can set valuetypes to a value, or null.  Their usage is entirely straightforward.  For instance, to use a nullable int simply declare a variable of type int?, and then set it to a value or null.

 

Behind the scenes, nullable valuetypes are implemented as generic structs of type Nullable<T>.  When you write:

            int? i = 2;

What you’re really writing is:

            Nullable<int> i = new Nullable<int>(2);

When you assign a value to a pre-existing nullable int, like so:

            i = 4;

you’re really writing:

            i = new Nullable<int>(4);

And when you ‘get’ the value, like so:

            int j = i;

What you’re really writing is:

            int j = i.Value;

The Value is a get property, and assignment is by creating a new instance of Nullable<T>.  This is similar to strings in C#: presenting a reference type by valuetype semantics.

 

This is all fine for primitives like int, double, etc. But when using structs, an interesting problem is created that lets light in on the ‘magic’ of nullable valuetypes.

 

Let’s say I have a struct Point, implemented as so:

 

    struct Point

    {

        public int x, y;

        public Point(int px, int py)

        {

            x = px; y = py;

        }

        public override string ToString()

        {

            return "(" + x + ", " + y + ")";

        }

    }

 

Then, I write a program that creates an int and a Point, and alter them slightly:

 

        static void Main(string[] args)

        {

            int i = 3;

            Console.WriteLine(++i);   //increment then print it

 

            Point p = new Point(2, 4);

            p.x = 1; Console.WriteLine(p);  //move left then print it

 

            Console.ReadLine();

        }

 

No problems here.  The int and the Point will be altered as expected.   Now I decide that I want my int and Point to be nullable.  So I simply add two question marks, producing this program:

 

        static void Main(string[] args)

        {

            int? i = 3;

            Console.WriteLine(++i);   //increment then print it

 

            Point? p = new Point(2, 4);

            p.x = 1; Console.WriteLine(p);  //move left then print it

 

            Console.ReadLine();

        }

 

But this won’t compile.  The int? part is fine, but for the Point? part I get the error: “'System.Nullable< Point>' does not contain a definition for 'x'”.  No problem, I’ll just edit it like so:

 

        static void Main(string[] args)

        {

            int? i = 3;

            Console.WriteLine(++i);   //increment then print it

 

            Point? p = new Point(2, 4);

            p. Value.x = 1; Console.WriteLine(p);  //move left then print it

 

            Console.ReadLine();

        }

 

Now you get a different compile error: “Cannot modify the return value of 'System.Nullable<.Point>.Value' because it is not a variable”.  This happens because Nullable<T>.Value has no set accessor method.

 

Nullable value types have this ‘limitation’ for a very good reason.   The same thing happens is you try to edit a struct in a List<> of structs.  Consider an over-simplification of Nullable<T> with a bodged-in set accessor, like so:

 

    class MyNullable<T>

    {

        private T val;

        public T Value

        {

            set {

                val = value;

            }

            get {

                return val;

            }

        }

    }

 

If I create a MyNullable<Point> and use it:

 

            MyNullable<Point> p = new MyNullable<Point>();

            p.Value = new Point(2, 1);

            p.Value.x = 2;

 

I get a compile error on the third line: “Cannot modify the return value of ‘MyNullable<Point>.Value' because it is not a variable”.  This is because the statement p.Value.x = 2 is equivilent to:

 

            Point otherPoint = p.Value; otherPoint.x = 2;

 

Which doesn’t change the underlying p.val, because otherPoint is a copy of p.Val.  So when C# gives us a compile error, it is merely protecting us from editing an irrelevent copy of the underlying Point.

 

In conclusion, nullable valuetypes are immutable.  I think the C# designers intended programmers to never modify the value of a struct once one is created, nullable or not.

Comments

 

Reflective Perspective - Chris Alcock » The Morning Brew #233 said:

November 28, 2008 2:11 AM
 

Weekly Link Post 70 « Rhonda Tipton’s WebLog said:

November 30, 2008 8:37 PM
 

Skeebo06 said:

I'm not sure I follow what you're trying to do.  A Struct is a pared down class, not a datatype or valuetype.  What would you gain it if you were able to create a nullable stuct?
December 2, 2008 8:35 AM
 

Jason Crease said:

Hi Skeebo.  Thanks for your comment.  In classic C++, structs are pared-down classes.  However in .NET structs are valuetypes, and classes are reference types.  Structs derive from System.ValueType.  For a more information, see: http://msdn.microsoft.com/en-us/library/saxz13w4(VS.80).aspx

Since structs are valuetypes, they cannot be made null (much like ints, doubles, etc).  Hence the invention of 'nullable valuetypes'.  An example of a use case is in database interaction.  In a database, a boolean value may have the values: true, false or null.  Nullable bools in C# allow you to make a boolean true, false or null.
December 2, 2008 8:45 AM
 

Ben Amada said:

Hi.  In the last example, on line 3 where the compiler error occurs, I don't understand why the compiler doesn't create a copy of p.Val, assign 2 to 'x' and assign this new Point struct to p.Val (overwriting the old p.Val struct)?
December 8, 2008 8:21 PM
You need to sign in to comment on this blog

About Jason Crease

Jason Crease joined Red-Gate in 2006, and works as a software tester in the .NET division. He is currently working on ANTS Profiler 4.

















<November 2008>
SuMoTuWeThFrSa
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456
A SysAdmin's Guide to Change Management
 In the first in a series of monthly articles, ‘Confessions of a Sys Admin’, Matt describes the issues... Read more...

Exchange: Recovery Storage Groups
 It can happen at any time: You get a request, as Admin, from your company, to provide the contents of... Read more...

Build Your Own Virtualized Test Lab
 Desmon explains the fundamentals of building a test lab for Windows servers and Enterprise applications... Read more...

Rendering Hierarchical Data with the Treeview
 It sometimes happens that Web Server controls that visualize data don't quite fit with the way that... Read more...

SQL Server 2008: Performance Data Collector
 With Performance Data Collector in SQL Server 2008, you can now store performance data from a number of... Read more...