Nullable泛型結構二彈

探討一些 System.Nullable<T> 結構 的額外課題。

前一篇講了一堆關於 System.Nullable<T> 結構的基本內容,這邊來補充一下前篇遺漏的問題。
 

(1) 宣告 Nullable<int> x = null; 的情形

這個宣告的運作和 Nullable<int> x = new Nullable<int>(); 是同義的,就是呼叫 initobj 指令。也就是說當你將一個 null 指派給 Nullable<T> 的變數時,結果就是 value Filed 恢復成該結構型別預設值,而 hasValue Field 則成為 false。

(2) 結構型別的影響

Nullable<T> 身為一個結構型別,常常會有很多讓初學者搞不清楚的地方,所以藉由這篇文來探討一下這些狀況。
如果我寫下這麼一段程式碼:

        static void Main(string[] args)
        {
            Nullable<int> x = 100;
            int y = x.Value;
            y = 1000;
        }

此時如果有人問你,在 y = 1000; 後,如果取出 x.Value 的屬性值會是多少?這問題應該不會有人答錯,這個答案是 100。

但如果這個 T 是個自訂結構呢? 例如:
 

    class Program
    {
        static void Main(string[] args)
        {
            Nullable<MyPoint> nullablePoint = new MyPoint(100, 100);
            MyPoint point = nullablePoint.Value;
            point.X = 999;
            point.Y = 999;
            Console.ReadLine();
        }


        public struct MyPoint
        {
            public MyPoint (int x, int y)
            {
                this.X = x;
                this.Y = y;
            }
            public int X
            { get; set; }
            public int Y
            { get; set; }
        }
    }

在設定了 point.X 與 point.Y 屬性後,如果去取得 nullablePoint.Value 的 X 與 Y,答案將會是甚麼?這時大概就會出現五五波了,有人會認為是(100,100) ,有人會認為是 (999,999);正確的答案是 (100,100)。
會以為是 (999,999) 的人通常都是對結構型別的特性不了解引起的,但是如果是 Nullable<int> 的時候卻不會答錯,這個現象很有趣,第一題答 100 且第二題答 (100,100) 的人通常是用『記憶』在寫程式,而非真的理解。初學時多半都會寫過這樣的程式碼:
 

            int x = 100;
            int y = x;
            y = 999;

所以你會記得在這個情形下之後程式碼 x 與 y 的變化不會影響彼此,但從來沒想過為什麼。

結構是一種實值型別,實值型別的物件有一個特性是它和變數是一體的,所以當你把某個實值型別的變數指派給另外一個變數的時候,他會把內容整個複製過去,而不是兩個變數指向同一個物件。

接著我們改寫上方的程式如下:
 

 class Program
    {
        static void Main(string[] args)
        {
            Nullable<MyPoint> nullablePoint = new MyPoint(100, 100);
            nullablePoint.Value.ChangeX(800);          
            Console.WriteLine(nullablePoint.Value.X);          
            Console.ReadLine();
        }

        public struct MyPoint
        {
            public void ChangeX(int x)
            {
                X = x;
            }

            public MyPoint(int x, int y)
            {
                this.X = x;
                this.Y = y;
            }
            public int X
            { get; set; }
            public int Y
            { get; set; }
        }
    }

這個 nullablePoint.Value.X 的結果是多少,800 ?抑或是 100 ? 答案是 100,原因和前述的內容相同;當使用了 nullablePoint.Value 時記憶體內就複製了一份完整的副本了,記得嗎? 所以呼叫的根本是副本的 ChangeX Method,而不是原來 nullablePoint 內部的 value Field 的 ChangeX Method。

如果要把那個程式碼拆開來看,是類似以下這樣的行為:
 

        static void Main(string[] args)
        {
            Nullable<MyPoint> nullablePoint = new MyPoint(100, 100);
            MyPoint point = nullablePoint.Value; 
            point.ChangeX(800);          
            Console.WriteLine(nullablePoint.Value.X);
            Console.ReadLine();
        }

這樣就很容易懂為什麼答案不是 800 了吧。