2017年9月11日 星期一

C# : 物件相等 Object Equal 的測試


了解更多關於 [Object.Equals Method
了解更多關於 [C# 程式設計手冊 

在 C# 中,我們知道由類別實例化所生成的執行個體(Instance)或稱物件(Object),在進行物件相等比較的時候,使用的參考位址比較,也就是說,若兩個物件變數由類別生成的物件若說他們相等,則表示他們兩個物件變數指向同一個參考物件。可是,由於結構是屬於實值型別(Value Type),所以,當要進行兩個由結構所生成的物件的時候,預設的相等比較是會進行該結構成員內的逐一進行相等比對,在這份筆記中,我們來實際檢測這樣的特色。
在這裡,我們定義兩個結構
  • MyStruct
    這個結構內,宣告了兩個屬性,並且透過了建構函式進行這兩個屬性的內容初始化。
  • MyStructWithEqualOperator
    這個結構內,同樣的,宣告了兩個屬性,並且透過了建構函式進行這兩個屬性的內容初始化。不過,這裡使用了多載運算子 public static bool operator == 與 public static bool operator != 的宣告,進行宣告了這個結構可以使用的新的 == / != 運算子的處理邏輯。在 == 運算子中,我們使用了結構物件(這裡使用的是 MyStructWithEqualOperator 型別的物件,而不是 object) 的 Equals 方法來進行兩個物件的比較。
public struct MyStruct
{
    public int X { get; set; }
    public int Y { get; set; }
    public MyStruct(int x, int y)
    {
        X = x;
        Y = y;
    }
}
    public struct MyStructWithEqualOperator
    {
        public int X { get; set; }
        public int Y { get; set; }
        public MyStructWithEqualOperator(int x, int y)
        {
            X = x;
            Y = y;
        }

        public static bool operator ==(MyStructWithEqualOperator c1, MyStructWithEqualOperator c2)
        {
            return c1.Equals(c2);
        }

        public static bool operator !=(MyStructWithEqualOperator c1, MyStructWithEqualOperator c2)
        {
            return !c1.Equals(c2);
        }
    }
另外,我們宣告了類別 MyClass,這個類別如同上述的結構一樣,有兩個屬性,可以透過建構式來進行初始化。
public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }
    public MyClass(int x, int y)
    {
        X = x;
        Y = y;
    }
}

進行測試 類別物件比較

首先我們要進行的測試為類別的物件比較,在這裡,我們產生兩個類別物件,這兩個物件擁有同樣的屬性值(這兩個屬性屬於 int 的型別),不過,這兩個物件在堆積(Heap)是分別佔據不同的記憶體空間,也可以說,這兩個物件變數分別指向了兩個不同實體。相關測試的程式碼如下所示。
Console.WriteLine("參考型別的物件比較");
MyClass classObject1 = new MyClass(10, 20);
MyClass classObject2 = new MyClass(10, 20);
MyClass classObject3;
classObject3 = classObject2;

Console.WriteLine($"classObject1==classObject2 is {classObject1 == classObject2}");
Console.WriteLine($"classObject1.Equals(classObject2) is {classObject1.Equals(classObject2)}");
Console.WriteLine($"object.Equals(classObject1, classObject2) is {object.Equals(classObject1, classObject2)}");
Console.WriteLine($"object.ReferenceEquals(classObject1, classObject2) is {object.ReferenceEquals(classObject1, classObject2)}");

Console.WriteLine("比較參考到同一個實體物件");
Console.WriteLine($"classObject3==structObject2 is {classObject3 == classObject2}");
Console.WriteLine($"classObject3.Equals(classObject2) is {classObject3.Equals(classObject2)}");
Console.WriteLine($"object.Equals(classObject3, classObject2) is {object.Equals(classObject3, classObject2)}");
Console.WriteLine($"object.ReferenceEquals(classObject3, classObject2) is {object.ReferenceEquals(classObject3, classObject2)}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
接著,我們進行了底下的測試,這個是關於兩個不同參考物件(但是成員屬性內容相同)的比較
  • classObject1==classObject2
    這裡使用了靜態運算子方法來進行這兩個不同實體比較,因為預設為參考比較,所以得到的結果為 False。
  • classObject1.Equals(classObject2)
    這裡使用物件本身的 Equals 方法來進行不同實體物件比較,因為預設為參考比較,所以得到的結果為 False。
  • object.Equals(classObject1, classObject2)
    這裡使用最上層類別,object 的 Equals 靜態方法來進行不同實體物件比較,因為預設為參考比較,所以得到的結果為 False。
  • object.ReferenceEquals(classObject1, classObject2)
    這裡使用最上層類別,object 的 ReferenceEquals 靜態方法來進行不同實體物件比較,所以得到的結果為 False。
這裡是這個測試的執行結果
參考型別的物件比較
structObject1==structObject2 is False
classObject1.Equals(classObject2) is False
object.Equals(classObject1, classObject2) is False
object.ReferenceEquals(classObject1, classObject2) is False
然後,我們進行了底下的測試,這個是關於兩個相同參考物件的比較,我們使用敘述 classObject3 = classObject2; 將 classObject3 與 classObject2 兩個物件變數設定成為參考到同一個物件實體。
  • classObject3==structObject2
    這裡使用了靜態運算子方法來進行這兩個相同實體比較,因為預設為參考比較,所以得到的結果為 True。
  • classObject3.Equals(classObject2)
    這裡使用物件本身的 Equals 方法來進行相同實體物件比較,因為預設為參考比較,所以得到的結果為 True。
  • object.Equals(classObject3, classObject2)
    這裡使用最上層類別,object 的 Equals 靜態方法來進行相同實體物件比較,因為預設為參考比較,所以得到的結果為 True。
  • object.ReferenceEquals(classObject3, classObject2)
    這裡使用最上層類別,object 的 ReferenceEquals 靜態方法來進行相同實體物件比較,所以得到的結果為 True。
這裡是這個測試的執行結果
比較參考到同一個實體物件
classObject3==structObject2 is True
classObject3.Equals(classObject2) is True
object.Equals(classObject3, classObject2) is True
object.ReferenceEquals(classObject3, classObject2) is True

進行測試 結構物件比較

現在,我們要來測試結構的物件比較,在這裡,我們使用建構式,建立了兩個結構物件。
Console.WriteLine("結構型別的物件比較");
MyStruct structObject1 = new MyStruct(10, 20);
MyStruct structObject2 = new MyStruct(10, 20);
MyStruct structObject3;
structObject3 = structObject2;

//Console.WriteLine($"structObject1==structObject2 is {structObject1 == structObject2}");
Console.WriteLine($"structObject1.Equals(structObject2) is {structObject1.Equals(structObject2)}");
Console.WriteLine($"object.Equals(structObject1, structObject2) is {object.Equals(structObject1, structObject2)}");
Console.WriteLine($"object.ReferenceEquals(structObject1, structObject2) is {object.ReferenceEquals(structObject1, structObject2)}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
接著,我們進行了底下的測試,這個是關於兩個不同參考物件(但是成員屬性內容相同)的比較
  • structObject1==structObject2
    當您使用這個敘述的時候,Visual Studio 會回報您有錯誤產生。
    錯誤 CS0019 運算子 '==' 不可套用至類型為 'MyStruct' 和 'MyStruct' 的運算元 ObjectEqual
    所以,若您沒有做任何多載 == 運算子的程式碼,您是無法在客製化結構中,使用這個運算子。
  • structObject1.Equals(structObject2)
    這裡使用物件本身的 Equals 方法來進行不同實體物件比較,因為預設為實值比較,所以會針對該結構內的成員進行實際擁有值的比較,所以得到的結果為 True
  • object.Equals(structObject1, structObject2)
    這裡使用最上層類別,object 的 Equals 靜態方法來進行不同實體物件比較,因為預設為實值比較,所以會針對該結構內的成員進行實際擁有值的比較,所以得到的結果為 True
  • object.ReferenceEquals(structObject1, structObject2)
    這裡使用最上層類別,object 的 ReferenceEquals 靜態方法來進行不同實體物件比較,所以得到的結果為 False。
這裡是這個測試的執行結果
結構型別的物件比較
structObject1.Equals(structObject2) is True
object.Equals(structObject1, structObject2) is True
object.ReferenceEquals(structObject1, structObject2) is False

進行測試 結構物件比較(有多載 == != 運算子)

在上面的測試中,我們看到了,結構預設無法使用 == 或者 != 運算子來進行物件內容比較,所以,在這個測試中,我們使用了客製化結構 MyStructWithEqualOperator,在這個結構內,有多載 == != 運算子的宣告,讓我們可以在結構的 == 與 != 運算子,來進行物件的比較。
Console.WriteLine("結構型別的物件比較(有多載等於與不等於的運算子)");
MyStructWithEqualOperator structWithEqualOperatorObject1 = new MyStructWithEqualOperator(10, 20);
MyStructWithEqualOperator structWithEqualOperatorObject2 = new MyStructWithEqualOperator(10, 20);
MyStructWithEqualOperator structWithEqualOperatorObject3;
structWithEqualOperatorObject3 = structWithEqualOperatorObject2;

Console.WriteLine($"structWithEqualOperatorObject1==structWithEqualOperatorObject2 is {structWithEqualOperatorObject1 == structWithEqualOperatorObject2}");
Console.WriteLine($"structWithEqualOperatorObject1.Equals(structWithEqualOperatorObject2) is {structWithEqualOperatorObject1.Equals(structWithEqualOperatorObject2)}");
Console.WriteLine($"object.Equals(structWithEqualOperatorObject1, structWithEqualOperatorObject2) is {object.Equals(structWithEqualOperatorObject1, structWithEqualOperatorObject2)}");
Console.WriteLine($"object.ReferenceEquals(structWithEqualOperatorObject1, structWithEqualOperatorObject2) is {object.ReferenceEquals(structWithEqualOperatorObject1, structWithEqualOperatorObject2)}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();

經過多載 == != 運算子之後,結構所產生的執行個體,就可以進行使用這兩個運算子了
結構型別的物件比較(有多載等於與不等於的運算子)
structWithEqualOperatorObject1==structWithEqualOperatorObject2 is True
structWithEqualOperatorObject1.Equals(structWithEqualOperatorObject2) is True
object.Equals(structWithEqualOperatorObject1, structWithEqualOperatorObject2) is True
object.ReferenceEquals(structWithEqualOperatorObject1, structWithEqualOperatorObject2) is False

了解更多關於 [Object.Equals Method
了解更多關於 [C# 程式設計手冊 











2017年9月8日 星期五

.NET C# 常數 const 與 唯讀 readonly

常數 const 與 唯讀 readonly 在應用程式執行上,所表現出來的最終行為是一樣的,那就是這兩個物件的值,在執行時期是無法變更的;可是,對於這兩種類型的物件的內在特型與執行時候的 IL 程式碼,卻有著處理上的很大不同。

了解更多關於 [const 關鍵字
了解更多關於 [readonly 關鍵字
了解更多關於 [C# 程式設計手冊

在這裡,我們宣告了一個類別,這個類別裡面只有兩個成員,一個是 const 的整數欄位成員,另外一個是 readonly 的整數常數欄位成員。
我們可以從微軟官方的文件中看到底下敘述
欄位會在呼叫物件執行個體的建構函式之前立即初始化。 如果建構函式指派了欄位的值,該值會覆寫欄位宣告期間所指定的任何值。
所以,在我們這個範例中,並沒有建置建構函式,而是提供了欄位的初始化設定,讓這兩個欄位擁有預先設定的內容。
    public class MyClass
    {
        public const int MyConstantString = 15;
        public readonly int MyReadonlyString = 168;
    }
接著,我們建立了 MyClass 這個類別物件,myObject,接著要來存取這個類別內的常數與唯讀欄位,此時,就會看到這兩者欄位的其中一個不同點。
當您在類別內宣告了一個常數欄位,這個常數欄位就具備了靜態特性,也就是說,我們要存取常數欄位,需要使用 類別名稱.欄位名稱 的方式來存取;而唯讀欄位則是透過執行個體才能夠存取,就是,在每個執行個體內,都會有這樣一個欄位,它是屬於唯讀的(這個欄位的值,需要在建構函式內進行初始化或者可以在宣告欄位時,使用指派運算子來指定欄位的初始值)
            MyClass myObject = new MyClass();

            int tmpString = MyClass.MyConstantString;
            tmpString = myObject.MyReadonlyString;
最後我們來看看進行存取常數欄位的 IL 中繼語言程式碼和唯讀欄位的 IL 中繼語言程式碼
這個是常數 IL 中繼語言程式碼,我們可以看到,我們要把常數整數 15 設定到變數 tmpString 內;因為這個是常數欄位,所以,編譯器直接把 15 這個整數值,轉換到 IL 中繼語言程式碼上了。
mov dword ptr [ebp-44h], 0Fh

這個是唯讀 IL 中繼語言程式碼,編譯器要透過位置定位方式,指出欄位 myObject.MyReadonlyString 所儲存的整數值為多少,這樣才能夠取出這個整數值,並且設定到 tmpString 變數內。
move eax,dword ptr [epb-40h]

了解更多關於 [const 關鍵字
了解更多關於 [readonly 關鍵字
了解更多關於 [C# 程式設計手冊

.NET C# 物件延遲產生 Lazy Initialization 的做法

當我們在進行 .NET 專案設計的時候,若在程式啟動的時候,設計了許多物件,此時會造成需要建立這些物件與進行初始化的工作,最後,就有可能表顯出這個應用程式啟動速度很慢;當然,還有很多情境,我們並不期望在我們宣告物件的時候,就進行物件的實例化(例如,該類別會耗用滿多的記憶體空間等等)。此時,我們可以使用 .NET 提供的 物件延遲產生 Lazy Initialization 技術。
在這個練習中,我們宣告了底下的類別,為了要能夠知道有執行個體產生出來,所以,我們在建構函式內,輸出訊息,讓我們知道這個類別的物件已經生成了。
另外,在這個類別中,我們建立了兩個建構函式,一個為預設建構函式,另外一個建構函式會接受兩個參數,不過在這個有接受參數的建構函式宣告中,我們又使用了 this() 來間接呼叫另外一個建構函式。
    public class MyClass
    {
        public string Name { get; set; }
        public int Value { get; set; }
        public MyClass()
        {
            Console.WriteLine("MyClass 執行個體已經生成了");
        }

        public MyClass(string name, int value):this()
        {
            Name = name;
            value = Value;
        }
    }

進行測試

接著,我們開始進行測試,首先我們執行底下程式碼。
在這裡,我們僅宣告與定義 Lazy<MyClass> 類別物件,這表示我們產生了一個物件延遲生成物件,而在泛型內所標示的型別,就是我們實際要使用型別物件;當底下程式碼執行之後,我們可以從控制台輸出畫面中,看到這個 MyClass 物件並沒有真的實例化一個執行個體。
            Console.WriteLine("宣告 物件延遲產生 物件變數");
            // // 這裡使用了 具名引數
            Lazy<MyClass> myClass = new Lazy<MyClass>(()=>new MyClass(value:100, name: "這是物件延遲產生")); 
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
接著,我們透過剛剛產生的 Lazy<MyClass> 類別物件,取得我們實際需要的執行個體 myClass.Value,一旦我們有存取這個屬性, MyClass 類別就會立即產生這個物件;我們可以從輸出訊息中,看到這個類別的建構函式確實有執行到。
            Console.WriteLine($"延遲物件的屬性值 Name={myClass.Value.Name} / Value={myClass.Value.Value}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
底下是執行結果的輸出內容
宣告 物件延遲產生 物件變數
Press any key for continuing...
MyClass 執行個體已經生成了
延遲物件的屬性值 Name=這是物件延遲產生 / Value=0
Press any key for continuing...

.NET C# 堆疊 Stack 與堆積 Heap 的記憶體耗盡測試


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





當我們在進行 .NET 應用程式開發過程中,經常會遇到這兩個錯誤訊息:StackOverflowException 與 OutOfMemoryException,他們分別表示了這個應用程式已經沒有足夠可用的記憶體,在這份練習中,將會展示出,造成這兩個例外異常的現象。
  • StackOverflowException
    當執行緒啟動執行的時候,預設會保留 1MB 的記憶體,這個記憶體就是所謂的堆疊 (Stack);而我們使用的資料型別若為 實值型別 (Value Type),這個時候,這些物件將會儲存在堆疊內;另外,當我們進行函式呼叫的時候,也會用到這個堆疊記憶體,只要結束函數執行,相關的記憶體會就自動歸還給堆疊,下次有其它函式要進行呼叫的時候,就可以重複繼續使用些堆疊記憶體。最後,.NET 記憶體回收機制,是不會來處理堆疊內的無參考記憶體的回收工作的。
  • OutOfMemoryException
    關於參考型別所實例化出來的執行個體(物件),當時的物件變數,實際上儲存的是堆積 (Heap) 上的某塊記憶體空間,也就是說,這個物件變數,儲存著一個參考,這個參考會指向堆積上的某個記憶體位置。當我們對於堆積上的物件不再使用的時候,也就是說,該物件沒有任何參考指向它,此時,.NET系統的 Garbage Collection 記憶體回收機制,會將這些沒有人參考的 Managed 記憶體進行回收,程式開發者不需要去處理這些記憶體回收的工作。因此,當堆積 Heap 的記憶體不夠用的時候,就會出現這個錯誤例外異常。
底下是我們要測式的兩種型別
  • MyStruct
    其型別為結構,屬於實值型別,也就是會使用堆疊記憶體。
  • MyLargeClass 與 MySmallClass
    其型別為類別,屬於參考型別,所產生的執行個體,將會儲存在堆積內。前者將會產生出大於 85000 bytes 的堆積空間的執行個體,而後者將會產生出小於 85000 bytes 的堆積空間的執行個體;我們將會使用這兩個類別所產生的執行個體,分別檢測 .NET 記憶體回收機制中,對於大型物件與小型物件的記憶體使用不足情況之測試。
    public struct MyStruct
    {
       public double X;
        public double Y;
    }

    public class MyLargeClass
    {
        // 這將會讓產生的執行個體會占用 > 85000 bytes 的記憶體大小
        public byte[] bytes = new byte[1024 * 86];
    }

    public class MySmallClass
    {
        // 這將會讓產生的執行個體會占用 < 85000 bytes 的記憶體大小
        public byte[] bytes = new byte[1024 * 20];
    }

進行測試

首先,我們來進行造成 StackOverflowException 例外異常的模擬測試,在這裡,我們設計一個遞迴呼叫函式,在這個函式裡面,會使用到一個結構物件,該結構物件將會耗用堆疊記憶體,由於該函式會無窮盡的呼叫自己,最終將會造成堆疊記憶體不足的情況發生。
        static void Main(string[] args)
        {
            long level = 0;
            Recursive(level);
        }

        private static void Recursive(long level)
        {
            MyStruct structObject;
            structObject.X = level;
            level++;
            Recursive(level);
        }
接著,我們來模擬堆積的記憶體耗盡的情境,由於 .NET 記憶體回收機制對於大型物件(大於 85000 Bytes)與小型物件的處理方式不同,所以,底下程式碼將會是進行大型物件的大量物件產生模擬;由於這些大物件產生之後,都會持續有參考到該物件,因此,GC是不會針對這些物件進行記憶體回收,最終會造成堆積記憶體耗盡,系統就會顯示 OutOfMemoryException 例外異常錯誤訊息。
            List<MyLargeClass> listObject = new List<MyLargeClass>();
            for (int i = 0; i < 1000 * 1000; i++)
            {
                listObject.Add(new MyLargeClass());
            }
        }

我們使用同樣的手法,只不過這次進行產生大量的小物件,並且這些小物件持續有參考指向該小物件,當然,最終還是會造成堆積記憶體耗盡,系統就會顯示 OutOfMemoryException 例外異常錯誤訊息。
            List<MySmallClass> listObject = new List<MySmallClass>();
            for (int i = 0; i < 1000 * 1000; i++)
            {
                listObject.Add(new MySmallClass());
            }
        }

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







2017年9月7日 星期四

.NET C# 靜態建構函式 static constructor 與執行個體建構函式 Instance Constructor

當我們在類別中,使用到了 static 關鍵字,不論用於類別或者類別成員上,都可以透過靜態建構函式,來進行這些靜態成員做初始化的動作。

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



若該類別沒有標示 static,則這個類別裡面可以同時擁有執行個體建構函式與靜態建構函式。
如同我們所知道的,若要產生一個類別的執行個體,需要使用 new 運算子與建構函式,此時,在物件生成的時候,會使用建構函式進行相關資料的初始化;可是,靜態建構函式則是甚麼時候才能夠被執行呢?當你掌握與了解靜態建構函式的執行時間,你就可以使用靜態建構函式,針對這些靜態資料成員,進行資料初始化。
在這裡,我們使用的類別範例,如下所示;在這個類別中,有個屬性,這個屬性的實際值將會儲存在一個欄位中,而這兩者都是靜態類型的資料。另外,我們也宣告了一個執行個體的屬性 Name,這個屬性必須透過物件個體,才能夠進行存取,靜態建構式是無法直接存取到這個屬性。最後,這裡也宣告了一個靜態方法 MyMethod,要呼叫這個方法,不需要透過執行個體來呼叫,只要使用類別名稱與句點,加上這個方法名稱,MyClass.MyMethod();,就可以執行這個靜態方法了。
對於建構函式方面,我們宣告了一個執行個體預設建構函式與一個靜態建構函式。
接下來,我們將要進行一些測試,了解靜態建構函式的執行時機。
    public class MyClass
    {
        private static int _ObjectCount;

        public static int ObjectCount
        {
            get
            {
                return _ObjectCount;
            }
            set
            {
                Console.WriteLine($"要設定屬性值 {value}");
                _ObjectCount = value;
            }
        }

        public string Name { get; set; }
        public MyClass()
        {
            Console.WriteLine($"執行個體建構函式被呼叫了");
        }

        static MyClass()
        {
            ObjectCount = 100;
            Console.WriteLine($"靜態建構函式被呼叫了");
        }

        public static void MyMethod()
        {

        }
    }

測試說明

  • 沒有存取或呼叫與這個類別有關的動作
    在這個測試範例中,我們不會使用程式碼進行任何與 MyClass 類別有關的存取或者呼叫動作,在這樣的情境之下,我們來看看,靜態建構函式是否會被執行呢?
    如果你把靜態建構成員當作傳統的全域性變數來看,理論上,只要在程式一起動的時候,這些全域性變數式需要進行初始化執行動作。
    可是,當你執行底下這段程式碼的時候,控制台視窗內,卻只有 Press any key for continuing... 訊息輸出,這表示,MyClass 的靜態建構函式,並沒有被執行,也就是說,任何靜態資料成員,還沒有機會做到初始化動作。
        static void Main(string[] args)
        {
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    
  • 存取靜態屬性或者呼叫靜態方法
    接下來的測試程式碼,將會加入呼叫靜態方法 MyClass.MyMethod(); 或者存取一個靜態屬性或者靜態欄位 Console.WriteLine($"靜態屬性:{MyClass.ObjectCount}");
        static void Main(string[] args)
        {
            MyClass.MyMethod();
            Console.WriteLine($"靜態屬性:{MyClass.ObjectCount}");

            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
   
底下是呼叫靜態方法的輸出結果
我們可以看到,當只要程式碼有執行這個類別的靜態方法,這個靜態建構函式將會被自動呼叫,而且,更重要的是,因為它是靜態建構函式,所以,他只會被執行一次,之後不論你呼叫幾次靜態方法,這個靜態建構函式都不會再執行。
要設定屬性值 100
靜態建構函式被呼叫了
Press any key for continuing...
底下是存取了靜態屬性的輸出結果
我們可以看到,當只要程式碼有存取這個類別的靜態屬性或者靜態欄位,這個靜態建構函式將會被自動呼叫,而且,更重要的是,因為它是靜態建構函式,所以,他只會被執行一次,之後不論你存取幾次靜態屬性或者靜態欄位,這個靜態建構函式都不會再執行。
要設定屬性值 100
靜態建構函式被呼叫了
靜態屬性:100
Press any key for continuing...
  • 建立此類別的執行個體
    最後,我們在執行程式碼中,只會建立一個 MyClass 類別的執行個體,讓我們來看看,會發生甚麼狀況?
        static void Main(string[] args)
        {
            MyClass object1 = new MyClass();

            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    
此時,您會看到該執行個體的建構函式會最後才被執行,而該類別的靜態建構函式會先被執行。
+

因此,我們可以得知,當程式碼有存取靜態物件、呼叫靜態方法、建立執行個體的時候,靜態建構函式會被自動呼叫,優先進行該類別的靜態成員的初始化;而且,這個靜態建構函式也只會在整個應用程式生命週期內,只會執行一次。
要設定屬性值 100
靜態建構函式被呼叫了
執行個體建構函式被呼叫了
Press any key for continuing...


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