2013年7月26日 星期五

Type Constructor

Type Constructor (或稱 static constructor) 並不會在程式啟動時馬上就被呼叫. 而是在

1. 任何該 Type 的 static properties/methods/fields 第一次被存取時
2. 任何該 Type 的 non-static methods/properties/indexers/constructors 第一次被呼叫時

由 JIT Compiler 在呼叫點之前【植入】額外的程式碼來呼叫 Type Constructor. 

CLR 保證:
1. Type Constructor 在每一個 AppDomain 只會被呼叫一次
2. Type Constructor 是 thread-safe 的

CLR 不保證:
1. Type Constructor 被呼叫的順序

若希望 CLR 執行 Type Constructor, 可參考下列程式碼
namespace ConsoleApplication1 {
    class A {
        static A() { Console.WriteLine("static A()"); }
    }
    class Program {
        static void Main(string[] args) {
            System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(A).TypeHandle);
            Console.ReadKey();
        }
    }
}

但請注意!!! CLR 絕對不會在同一個 AppDomain 裡呼叫同一個 TypeConstructor 兩次, 也不保證 TypeConstructor 之間的呼叫順序.因此上面那個 RunClassConstructor 若使用第二次的話是完全無作用的.

-----------------------------------------------------------------------------
對於這個 class 
class Dummy {
    private static Int32 val = 5;
}

表面上他並沒有宣告 type constructor, 
但為了初始化 val,
C# Compiler 其實會偷偷的幫它產生一個 type constructor.

出處: CLR via C# 4Ed Chapter 8

關於常數的兩三事

定義常數時,若該數值永遠不可能改變則使用 const,否則建議使用 readonly。

假設某 A.DLL 內含一 class 如下:
public sealed class Consts {
    public static readonly Int32 const1 = 50;
    public const Int32 const2 = 50;
}

某程式 B 使用了 A.DLL 內的 Consts,如下:
public void Test()  {
    Console.WriteLine(Consts.const1);
    Console.WriteLine(Consts.const2);
}

這個函式會印出
50
50

一段時間之後,A.DLL 發行新版本,Consts 被改成這樣:
public sealed class Consts {
    public static readonly Int32 const1 = 10;
    public const Int32 const2 = 10;
}

若程式 B 沒有 recompile,而使直接使用新版的 A.DLL 的話,輸出結果會是:
10
50

這是因為,const 會直接鑲進目標程式的 code 裡(效能好),readonly 則是會執行期才去取值(效能較差)。B 程式其實等價於:
public void Test()  {
    Console.WriteLine(Consts.const1);
    Console.WriteLine(50);
}

因此除非 B 做 recompile,否則 A.DLL 對於 Consts.const2 的改變不會被程式 B 看見。

出處: CLR via C# 4Ed Chapter 7

Identity And Equality

先定義兩個名詞:
Identity - 兩個變數是否參考到同一個物件
Equality - 兩個變數參考到的物件的值是不是相等

System.Object.Equals() 基本上實做的是 Identity,他的 code 大概長這樣:

public class Object {
    public virtual Boolean Equals(Object rhs) {
        if(this == rhs) return true;
        return false;
    }
}

由於子代有可能 override System.Object.Equals() 以及各種比較算符 (== 或 !=),因此若要判斷兩個變數是否參考到同一個物件 (Identity),請不要使用 Equals() 或 ==,請使用 Object.ReferenceEquals(),他的 code 大概長這樣:

public class Object {
    public static Boolean ReferenceEquals(Object lhs, Object, rhs) {
        return (lhs == rhs);
    }
}

System.ValueType 是 System.Object 的子代,它 override 了 Equals() 這個 method,讓它變成實作 Equality。但由於 System.ValueType.Equals() 是靠 reflection 來實作 Equals(),因此它的效率很差。因此當程式設計師設計了自己的 Value Type,而且該 ValueType 有比較的需求時,最好提供一個 override 的 Equals() method。

大部份的 class 都沒有比較 Equality 的需求,因此不太需要 override Equals()。但 Value Type 通常會有 override Equals() 的需求。當您要 override Equals() 時,請注意下列事項:

* Reflexive -> x.Equals(x) 必須是 true
* Symmetric -> 若 x.Equals(y), 則 y.Equals(x) 必須為 true
* Transitive -> 若 x.Equals(y) 且 y.Equals(z),則 x.Equals(z) 必須為 true
* Consistent -> 只要物件沒被改變,兩次 Equals() 的結果應該要一樣。

以下是通常要做的額外項目:
* 您應該順便繼承及實作 System.IEquatable<T>
* 您應該要 override GetHashCode(),並且保證:
    # 若兩個物件 Equals(),他們的 GetHashCode() 的回傳值必須相同。
    # 只要物件未被更改,兩次 GetHashCode() 的回傳值必須相同。
* 您應該順便實作 == 算符與 != 算符
* 如果您的 type 可以被排序:
    # 您應該繼承及實做 System.IComparable
    # 您應該繼承及實做 System.IComparable<T>
    # 您應該實做所有的比大小算符,包含 <, <=, >, >=

以下是一個 override Equals 的範例. 為求簡單, 我只專注在 Equals 以及 GetHashCode 這兩個 method 上, 請務必仔細研讀!!!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {
    class Program {
        class Base {
            private Int32 val;
            public Base(Int32 val) { this.val = val; }
            // 因為父類別是 Object, 他的 Equals 其實是檢查 identity
            // 所以在這裡不應該呼叫
            public override bool Equals(object obj) {
                if(obj == null) return false;
                if(Object.ReferenceEquals(this, obj)) return true;
                if(this.GetType() != obj.GetType()) return false;
                if(this.val != ((Base)obj).val) return false;
                return true;
            }
            // 因為父類別是 Object, 
            // 他的 GetHashCode 其實是透過 this 指標的位址求得,
            // 故在這裡不應該呼叫
            public override int GetHashCode() {
                return this.val.GetHashCode();
            }
        };
        class Drived : Base {
            private Int32 val;
            public Drived(Int32 valOfDrived, Int32 valOfBase) : base(valOfBase) {
                this.val = valOfDrived;
            }
            // 請注意!!! 父類別是否是 System.Object 會影響到 Equals 的實作方式
            public override bool Equals(object obj) {
                if(!base.Equals(obj)) return false;
                if(this.val != ((Drived)obj).val) return false;
                return true;
            }
            // 請注意!!! 父類別是否是 System.Object 會影響到 GetHashCode 的實作方式
            public override int GetHashCode() {
                return base.GetHashCode() ^ this.val.GetHashCode();
            }
        }
        static void Main(string[] args) {
            Base b = new Base(1);
            Console.WriteLine(b.Equals(new Base(1)));
            Console.WriteLine(b.Equals(new Base(2)));
            Console.WriteLine(b.Equals(new Drived(2, 1)));
            Console.WriteLine(b.Equals(b));
            Console.WriteLine(b.Equals(null));
            Console.WriteLine("------------------------------");
            Drived d = new Drived(2, 1);
            Console.WriteLine(d.Equals(new Base(1)));
            Console.WriteLine(d.Equals(new Drived(1, 1)));
            Console.WriteLine(d.Equals(new Drived(2, 1)));
            Console.WriteLine(d.Equals(d));
            Console.WriteLine(d.Equals(null));
            Console.ReadKey();
        }
    }
}


出處: CLR via C# 第四版第五章