2017年9月6日 星期三

.NET C# 結構 struct 的宣告與封裝和使用練習

在 C# 中,結構是使用 structClass 關鍵字來進行定義,這個型別屬於實值型別,有別於類別的參考型別(您需要能夠區分這兩種 實值型別 Value Type 與 參考型別 Reference Type的差異)。

了解更多關於 [結構
了解更多關於 [C# 程式設計手冊 

我們在這裡會進行結構的成員宣告與定義,其中最常用的就是 欄位屬性建構函式方法 的宣告。
在這個結構筆記中,我們定義了底下的結構
在這個結構中,我們定義了一個完整屬性 X 的宣告,你可以使用程式碼片段 propfull 來快速產生這個定義程式碼,這裡表示了屬性 X 的實際儲存內容,將會儲存在欄位 _X 內,當您要進行讀取/寫入動作的時候,是透過了 get存取子和set存取子 這兩個方法來間接存取欄位 _X。
另外,我們使用程式碼片段 prop 這個程式碼片段,宣告了一個自動建置的屬性 Y,在這樣的用法中,我們並不需要額外的定義一個欄位 _Y ,因為,編譯器會幫我們自動產生這些程式碼。使用了自動建置的屬性,大幅簡化了我們在屬性上的程式碼撰寫,也讓我們的程式碼更加的清爽,最重要的是,這兩種做法的最終效果,都是一樣的。
最後,我們定義了一個欄位 Z。
不過,與類別有點不同的地方,在要建立結構執行個體的時候,可以不需要使用 new 運算子與結構預設函式,就可以直接存取結構內的各個欄位,並且,在結構內,我們不能夠定義預設建構函式,但是,可以定義其他有參數的建構函式。
在這個結構範例程式碼中,您會看到我們並沒已宣告任何建構函式,此時,你可以直接使用預設建構函式來建立這個結構執行個體;然而,您會在底下的程式碼中看到有個有兩個參數的建構函式,若您將這個有參數的建構函式解除註解之後,您就可以在程式碼中,使用兩個引述與這個建構函式,來建立出這個結構的執行個體;不過,就在這個時候,您也無法繼續在使用預設建構函式來產生執行個體了。
最後,我們學習如何在結構中產生這個結構應該提供的行為,也就是方法。Length() 這個方法我們使用了 private 存取修飾詞,宣告這個函式僅能夠在結構內來呼叫,我們會在結構內的 Print()來呼叫,而 Print() 方法的存取修飾詞為 public,這表示,任何程式碼皆可以透過執行個體來執行這個方法。
最後,結構與類別有著很大的不同特性,您需要確實瞭解這兩個類型的相同與相異點,才能夠使用他們設計出符合您需要的型別與物件。
    public struct MyPoint
    {
        #region 座標點 (使用 region 來做到程式碼區隔)
        // 可以使用程式碼片段 propfull
        private double _X;

        public double X
        {
            get { return _X; }
            set { _X = value; }
        }

        // 可以使用程式碼片段 prop
        public double Y { get; set; }

        public double Z;
        #endregion

        // 底下的預設建構式,會產生甚麼錯誤訊息呢?
        //public MyPoint()
        //{
        //    _X = 0;
        //    Y = 0;
        //    Z = 0;
        //}

        // 底下的程式碼為何會出錯
        //public MyPoint(double x, double y)
        //{
        //    X = x;
        //    Y = y;
        //}

        /// <summary>
        /// 計算座標點長度
        /// (使用 XML 註解,提升程式碼閱讀性)
        /// 這個方法僅限類別內可以使用
        /// </summary>
        /// <returns></returns>
        private double Length()
        {
            return Math.Sqrt(X * X + Y * Y);
        }

        /// <summary>
        /// 顯示座標詳細資訊
        /// 任何程式碼都可以存取這個方法
        /// </summary>
        /// <returns></returns>
        public string Print()
        {
            return $"({X}, {Y}) 的長度是 {Length()}";
        }
    }

使用類別產生物件,進而存取類別內的資料與行為

這個程式碼 MyPoint fooObject4; 表示宣告一個物件變數 fooObject4,由於這個物件的型別是結構,因此,這個物件變數已經可以讓您直接存取該結構物件的內容。這樣的特性將會是與類別所不盡相同的。
另外,當執行底下程式碼
fooObject4 = fooObject1;
這表示了 fooObject4 會持有與 fooObject1 相同的物件值,不過,這個時候,這兩個物件變數,此時分別指向了不同的兩個物件。這樣的特性,也是與類別的用法不太一樣的。
在最後面,我們宣告了 MyPoint fooObject6; fooObject6 這個物件,接著,我們存取了其欄位 Z,您可以從 Visual Studio 中看到,這樣的行為是被允許的。
不過,當要存取屬性 X或者屬性 Y,卻會得到了
錯誤    CS0165    使用未指派的區域變數 'fooObject6'
這樣的錯誤訊息,這是因為,在這個時候,欄位的值,尚未被初始化,所以,會產生錯誤;這樣的用法也是和類別不太相同的。

最後,當我們宣告一個結構物件,有 new 運算子與建構函式或者 default,則這樣就可以得到一個已經完全初始化的結構物件,例如這樣的做法 MyPoint fooObject7 = new MyPoint() { X = 5, Y = 4 };。經過這樣獲得的結構物件,我們就可以存取該結構物件內的欄位與屬性了。
    class Program
    {
        static void Main(string[] args)
        {
            // 宣告一個 MyPoint 結構的物件變數,並且該變數持有這個所有成員的值
            MyPoint fooObject1 = new MyPoint() { X = 5, Y = 4 };

            // 宣告一個 MyPoint 類別的物件變數,並且指向新產生的 MyPoint 物件
            //MyPoint fooObject2 = new MyPoint(5, 4);
            // 宣告一個 MyPoint 類別的物件變數,並且指向新產生的 MyPoint 物件
            var fooObject3 = new MyPoint() { X = 5, Y = 4 };
            // 宣告一個 MyPoint 類別的物件變數,不需要使用任何 new 運算子與建構函式
            MyPoint fooObject4;
            // 將物件變數的參考值指向另外一個 MyPoint 物件
            fooObject4 = fooObject1;
            // 宣告一個 MyPoint 類別的物件變數,使用 Default
            MyPoint fooObject5 = default(MyPoint);

            // 在這裡,你可以呼叫 Length() 方法嗎?
            Console.WriteLine($"X:{fooObject1.X}, Y:{fooObject1.Y} / 更多詳細資訊 {fooObject1.Print()}");
            Console.WriteLine($"X:{fooObject3.X}, Y:{fooObject3.Y} / 更多詳細資訊 {fooObject3.Print()}");
            Console.WriteLine($"X:{fooObject4.X}, Y:{fooObject4.Y} / 更多詳細資訊 {fooObject4.Print()}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();

            Console.WriteLine("修改物件的內容");
            fooObject1.X = 7.0;
            fooObject3.X = 8.0;
            fooObject4.X = 9.0;
            Console.WriteLine($"X:{fooObject1.X}, Y:{fooObject1.Y} / 更多詳細資訊 {fooObject1.Print()}");
            Console.WriteLine($"X:{fooObject3.X}, Y:{fooObject3.Y} / 更多詳細資訊 {fooObject3.Print()}");
            Console.WriteLine($"X:{fooObject4.X}, Y:{fooObject4.Y} / 更多詳細資訊 {fooObject4.Print()}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();

            fooObject5.X = 10;
            fooObject5.Y = 20;
            Console.WriteLine($"X:{fooObject5.X}, Y:{fooObject5.Y} / 更多詳細資訊 {fooObject5.Print()}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();

            MyPoint fooObject7 = new MyPoint() { X = 5, Y = 4 };
            fooObject7.Z = 16;
            fooObject7.X = 16;
            fooObject7.Y = 20;

            MyPoint fooObject6;
            // 這裡為什麼可以設定成員 欄位的值
            fooObject6.Z = 16;
            // 這裡為什麼無法設定成員 屬性的值
            fooObject6.X = 16;
            // 若把上一行註解,這裡為什麼無法設定成員 屬性的值
            fooObject6.Y = 20;
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    }


了解更多關於 [結構
了解更多關於 [C# 程式設計手冊 






沒有留言:

張貼留言