Nullable Structs – An interesting ‘Gotcha’

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.

  • 6206 views

  • Rate
    [Total: 0    Average: 0/5]