C#入門06:大雜燴-MSDN上的C#編程指南

構造函數

靜態構造函數用於初始化任何靜態數據,或用於執行僅需執行一次的特定操做。在建立第一個實例或引用任何靜態成員以前,將自動調用靜態構造函數。程序員

class SimpleClass
{
    // Static constructor
    static SimpleClass()
    {
        //...
    }
}

靜態構造函數具備如下特色:
靜態構造函數既沒有訪問修飾符,也沒有參數。
在建立第一個實例或引用任何靜態成員以前,將自動調用靜態構造函數來初始化類。
沒法直接調用靜態構造函數。
在程序中,用戶沒法控制什麼時候執行靜態構造函數。
靜態構造函數的典型用途是:當類使用日誌文件時,將使用這種構造函數向日志文件中寫入項。
靜態構造函數在爲非託管代碼建立包裝類時也頗有用,此時該構造函數能夠調用 LoadLibrary 方法。編程

與有些語言不一樣,C# 不提供複製構造函數。若是您建立了新的對象並但願從現有對象複製值,您必須自行編寫適當的方法。設計模式

class Person
    {
        private string name;
        private int age;

        // Copy constructor.
        public Person(Person previousPerson)
        {
            name = previousPerson.name;
            age = previousPerson.age;
        }
    }

析構函數

析構函數用於析構類的實例。
不能在結構中定義析構函數。只能對類使用析構函數。
一個類只能有一個析構函數。
沒法繼承或重載析構函數。
沒法調用析構函數。它們是被自動調用的。
析構函數既沒有修飾符,也沒有參數。
例如,下面是類 Car 的析構函數的聲明:數組

class Car
{
    ~ Car()  // destructor
    {
        // cleanup statements...
    }
}

該析構函數隱式地對對象的基類調用 Finalize。這樣,前面的析構函數代碼被隱式地轉換爲:安全

protected override void Finalize()
{
    try
    {
        // cleanup statements...
    }
    finally
    {
        base.Finalize();
    }
}

這意味着對繼承鏈中的全部實例遞歸地調用 Finalize 方法(從派生程度最大的到派生程度最小的)。
不該使用空析構函數。若是類包含析構函數,Finalize 隊列中則會建立一個項。調用析構函數時,將調用垃圾回收器來處理該隊列。若是析構函數爲空,則只會致使沒必要要的性能丟失。
程序員沒法控制什麼時候調用析構函數,由於這是由垃圾回收器決定的。垃圾回收器檢查是否存在應用程序再也不使用的對象。若是垃圾回收器認爲某個對象符合析構,則調用析構函數(若是有)並回收用來存儲此對象的內存。程序退出時也會調用析構函數。
資源的顯示釋放
若是您的應用程序在使用昂貴的外部資源,則還建議您提供一種在垃圾回收器釋放對象前顯式地釋放資源的方式。可經過實現來自 IDisposable 接口的 Dispose 方法來完成這一點,該方法爲對象執行必要的清理。這樣可大大提升應用程序的性能。即便有這種對資源的顯式控制,析構函數也是一種保護措施,可用來在對 Dispose 方法的調用失敗時清理資源。
1)實現 Dispose 方法;
2)使用using 語句。app

靜態類和成員

靜態類和類成員用於建立無需建立類的實例就可以訪問的數據和函數。靜態類成員可用於分離獨立於任何對象標識的數據和行爲:不管對象發生什麼更改,這些數據和函數都不會隨之變化。當類中沒有依賴對象標識的數據或行爲時,就可使用靜態類。
類能夠聲明爲 static 的,以指示它僅包含靜態成員。不能使用 new 關鍵字建立靜態類的實例。靜態類在加載包含該類的程序或命名空間時由 .NET Framework 公共語言運行庫 (CLR) 自動加載。
使用靜態類來包含不與特定對象關聯的方法。例如,建立一組不操做實例數據而且不與代碼中的特定對象關聯的方法是很常見的要求。您應該使用靜態類來包含那些方法。
靜態類的主要功能以下:
它們僅包含靜態成員。
它們不能被實例化。
它們是密封的。
它們不能包含實例構造函數(C# 編程指南)。
所以建立靜態類與建立僅包含靜態成員和私有構造函數的類大體同樣。私有構造函數阻止類被實例化。
使用靜態類的優勢在於,編譯器可以執行檢查以確保不致偶然地添加實例成員。編譯器將保證不會建立此類的實利。
靜態類是密封的,所以不可被繼承。靜態類不能包含構造函數,但仍可聲明靜態構造函數以分配初始值或設置某個靜態狀態。有關更多信息,請參見靜態構造函數(C# 編程指南)。異步

靜態成員
即便沒有建立類的實例,也能夠調用該類中的靜態方法、字段、屬性或事件。若是建立了該類的任何實例,不能使用實例來訪問靜態成員。只存在靜態字段和事件的一個副本,靜態方法和屬性只能訪問靜態字段和靜態事件。靜態成員一般用於表示不會隨對象狀態而變化的數據或計算;例如,數學庫可能包含用於計算正弦和餘弦的靜態方法。ide

//靜態類中必須所有是靜態成員函數
    static class CompanyInfo
    {
        public static readonly string name;
        public static readonly string addr;

        //使用靜態構造函數初始化靜態類成員
        static CompanyInfo()
        {
            name = "BGI";
            addr = "Shenzhen";
            Console.WriteLine("InitCompanyInfo");
        }
    }

索引器

索引器容許類或結構的實例按照與數組相同的方式進行索引。索引器相似於屬性,不一樣之處在於它們的訪問器採用參數。
在下面的示例中,定義了一個泛型類,併爲其提供了簡單的 get 和 set 訪問器方法(做爲分配和檢索值的方法)。Program 類爲存儲字符串建立了此類的一個實例。函數

class SampleCollection<T>
{
    private T[] arr = new T[100];
    public T this[int i]
    {
        get
        {
            return arr[i];
        }
        set
        {
            arr[i] = value;
        }
    }
}

// This class shows how client code uses the indexer
class Program
{
    static void Main(string[] args)
    {
        SampleCollection<string> stringCollection = new SampleCollection<string>();
        stringCollection[0] = "Hello, World";
        System.Console.WriteLine(stringCollection[0]);
    }
}

索引器使得對象可按照與數組類似的方法進行索引。性能

  • get 訪問器返回值。set 訪問器分配值。
  • this 關鍵字用於定義索引器。
  • value 關鍵字用於定義由 set 索引器分配的值。
  • 索引器沒必要根據整數值進行索引,由您決定如何定義特定的查找機制。
  • 索引器可被重載。
  • 索引器能夠有多個形參,例如當訪問二維數組時。

提升索引器的安全性和可靠性有兩種主要的方法:
1)當設置並檢索來自索引器訪問的任何緩衝區或數組的值時,請始終確保您的代碼執行範圍和類型檢查。
2)應當爲 get 和 set 訪問器的可訪問性設置儘量多的限制。這一點對 set 訪問器尤其重要。
屬性和索引器的區別: 輸入圖片說明

委託

委託相似於 C++ 函數指針,但它是類型安全的。
委託容許將方法做爲參數進行傳遞。
委託可用於定義回調方法。
委託能夠連接在一塊兒;例如,能夠對一個事件調用多個方法。
方法不須要與委託簽名精確匹配。有關更多信息,請參見協變和逆變。
C# 2.0 版引入了匿名方法的概念,此類方法容許將代碼塊做爲參數傳遞,以代替單獨定義的方法。
與 C 中的函數指針不一樣,委託是面向對象的、類型安全的和保險的。 常見的委託聲明和調用:

// Create a method for a delegate.
public static void DelegateMethod(string message)
{
    System.Console.WriteLine(message);
}

// Instantiate the delegate.
Del handler = DelegateMethod;

// Call the delegate.
handler("Hello World");

委託調用方法:

1)命名方法

public delegate void TestDelegate();

    class Program
    {
        //打印當前日期和時間
        static void PrintPoint()
        {
            for (int i = 0; i < 5; i++)
            {
                DateTime dtCurr = DateTime.Now;
                Console.WriteLine("{0:yyyy-MM-dd HH:mm:ss.fff}", dtCurr);
                Thread.Sleep(1000);
            }
        }
    }

    //使用類Program的靜態函數PrintPoint()實例化委託
    TestDelegate method = Program.PrintPoint;
    method();

2)匿名委託
在 2.0 以前的 C# 版本中,聲明委託的惟一方法是使用命名方法。C# 2.0 引入了匿名方法。要將代碼塊傳遞爲委託參數,建立匿名方法則是惟一的方法。例如:

// Create a handler for a click event
button1.Click += delegate(System.Object o, System.EventArgs e)
                   { System.Windows.Forms.MessageBox.Show("Click!"); };

或:

// Create a delegate instance
delegate void Del(int x);

// Instantiate the delegate using an anonymous method
Del d = delegate(int k) { /* ... */ };

若是使用匿名方法,則沒必要建立單獨的方法,所以減小了實例化委託所需的編碼系統開銷。例如,若是建立方法所需的系統開銷是沒必要要的,在委託的位置指定代碼塊就很是有用。啓動新線程便是一個很好的示例。無需爲委託建立更多方法,線程類便可建立一個線程而且包含該線程執行的代碼。

void StartThread()
{
    System.Threading.Thread t1 = new System.Threading.Thread
      (delegate()
            {
                System.Console.Write("Hello, ");
                System.Console.WriteLine("World!");
            });
    t1.Start();
}

什麼時候使用委託而不使用接口:
在如下狀況中使用委託:
1)當使用事件設計模式時。
2)當封裝靜態方法可取時。
3)當調用方不須要訪問實現該方法的對象中的其餘屬性、方法或接口時。
4)須要方便的組合。
5)當類可能須要該方法的多個實現時。

在如下狀況中使用接口:
1)當存在一組可能被調用的相關方法時。
2)當類只須要方法的單個實現時。
3)當使用接口的類想要將該接口強制轉換爲其餘接口或類類型時。
4)當正在實現的方法連接到類的類型或標識時:例如比較方法。

委託的協變:
委託協變指的是返回類型是一個基類類型,由調用者實現的函數返回類型爲委託返回類型的派生類的一種委託調用模式:

class Mammals
{
}

class Dogs : Mammals
{
}

class Program
{
    // Define the delegate.
    public delegate Mammals HandlerMethod();

    public static Mammals FirstHandler()
    {
        return null;
    }

    public static Dogs SecondHandler()
    {
        return null;
    }

    static void Main()
    {
        HandlerMethod handler1 = FirstHandler;

        // Covariance allows this delegate.
        HandlerMethod handler2 = SecondHandler;
    }
}

委託逆變:
委託逆變和協變相反,是委託的參數是一個基類類型,而調用者傳遞的參數類型是委託參數類型的派生類:

System.DateTime lastActivity;
public Form1()
{
    InitializeComponent();

    lastActivity = new System.DateTime();
    this.textBox1.KeyDown += this.MultiHandler; //works with KeyEventArgs
    this.button1.MouseClick += this.MultiHandler; //works with MouseEventArgs

}

// Event hander for any event with an EventArgs or
// derived class in the second parameter
private void MultiHandler(object sender, System.EventArgs e)
{
    lastActivity = System.DateTime.Now;
}

事件

事件具備如下特色:
發行者肯定什麼時候引起事件,訂戶肯定執行何種操做來響應該事件。
一個事件能夠有多個訂戶。一個訂戶可處理來自多個發行者的多個事件。
沒有訂戶的事件永遠不會被調用。
事件一般用於通知用戶操做(如:圖形用戶界面中的按鈕單擊或菜單選擇操做)。
若是一個事件有多個訂戶,當引起該事件時,會同步調用多個事件處理程序。要異步調用事件,請參見使用異步方式調用同步方法。
能夠利用事件同步線程。
在 .NET Framework 類庫中,事件是基於 EventHandler 委託和 EventArgs 基類的。

發佈符合 .NET Framework 準則的事件:
.NET Framework 類庫中的全部事件均基於 EventHandler 委託,定義以下:

public delegate void EventHandler(object sender, EventArgs e);

事件只能在聲明事件的類中使用,及時是派生類也不能使用,派生類只能訂閱事件。若是須要在派生類中直接觸發事件,就須要在基類中將事件的觸發封裝起來,變成一個protected方法,而後派生類就能夠出發事件了:

//聲明委託
    public delegate void PrintHandle(string msg);

    class Animal
    {
        //聲明事件
        public event PrintHandle PrintEvent;

        //基類方法
        public virtual void Eat()
        {
            PrintHandle p = PrintEvent;
            if (p != null)
            {
                p(Name);
            }
        }

        //基類屬性
        public virtual string Name { get; set; }
    }

    class Dog : Animal
    {
        public Dog()
        {
            Name = "Dog";
        }
    }

    class Program
    {
        static void Main()
        {
            Dog d = new Dog();
            d.PrintEvent += delegate (string msg)
            {
                Console.WriteLine("Your name is {0}", msg);
            };
            d.Eat();

            Console.Read();
        }
    }

迭代器

建立迭代器最經常使用的方法是對 IEnumerable 接口實現 GetEnumerator 方法,例如:

public System.Collections.IEnumerator GetEnumerator()
{
    for (int i = 0; i < max; i++)
    {
        yield return i;
    }
}

static void Main()
{
    ListClass listClass1 = new ListClass();

    foreach (int i in listClass1.GetEnumerator())
    {
        System.Console.WriteLine(i);
    }
}

帶有參數的迭代器:

// Implementing the enumerable pattern
public System.Collections.IEnumerable SampleIterator(int start, int end)
{
    for (int i = start; i <= end; i++)
    {
        yield return i;
    }
}

ListClass test = new ListClass();
foreach (int n in test.SampleIterator(1, 10))
{
    System.Console.WriteLine(n);
}

使用不安全代碼

下面的示例經過讀取並顯示一個文本文件來演示 Windows ReadFile 函數。ReadFile 函數須要使用 unsafe 代碼,由於它須要一個做爲參數的指針。
傳遞到 Read 函數的字節數組是託管類型。這意味着公共語言運行庫 (CLR) 垃圾回收器可能會隨意地對數組使用的內存進行從新定位。爲了防止出現這種狀況,使用 fixed 來獲取指向內存的指針並對它進行標記,以便垃圾回收器不會移動它。在 fixed 塊的末尾,內存將自動返回,以便可以經過垃圾回收移動。
此功能稱爲「聲明式鎖定」。鎖定的好處是系統開銷很是小,除非在 fixed 塊中發生垃圾回收(但此狀況不太可能發生)。

class FileReader
{
    const uint GENERIC_READ = 0x80000000;
    const uint OPEN_EXISTING = 3;
    System.IntPtr handle;

    [System.Runtime.InteropServices.DllImport("kernel32", SetLastError = true)]
    static extern unsafe System.IntPtr CreateFile
    (
        string FileName,          // file name
        uint DesiredAccess,       // access mode
        uint ShareMode,           // share mode
        uint SecurityAttributes,  // Security Attributes
        uint CreationDisposition, // how to create
        uint FlagsAndAttributes,  // file attributes
        int hTemplateFile         // handle to template file
    );

    [System.Runtime.InteropServices.DllImport("kernel32", SetLastError = true)]
    static extern unsafe bool ReadFile
    (
        System.IntPtr hFile,      // handle to file
        void* pBuffer,            // data buffer
        int NumberOfBytesToRead,  // number of bytes to read
        int* pNumberOfBytesRead,  // number of bytes read
        int Overlapped            // overlapped buffer
    );

    [System.Runtime.InteropServices.DllImport("kernel32", SetLastError = true)]
    static extern unsafe bool CloseHandle
    (
        System.IntPtr hObject // handle to object
    );

    public bool Open(string FileName)
    {
        // open the existing file for reading       
        handle = CreateFile
        (
            FileName,
            GENERIC_READ,
            0,
            0,
            OPEN_EXISTING,
            0,
            0
        );

        if (handle != System.IntPtr.Zero)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public unsafe int Read(byte[] buffer, int index, int count)
    {
        int n = 0;
        fixed (byte* p = buffer)
        {
            if (!ReadFile(handle, p + index, count, &n, 0))
            {
                return 0;
            }
        }
        return n;
    }

    public bool Close()
    {
        return CloseHandle(handle);
    }
}

class Test
{
    static int Main(string[] args)
    {
        if (args.Length != 1)
        {
            System.Console.WriteLine("Usage : ReadFile <FileName>");
            return 1;
        }

        if (!System.IO.File.Exists(args[0]))
        {
            System.Console.WriteLine("File " + args[0] + " not found.");
            return 1;
        }

        byte[] buffer = new byte[128];
        FileReader fr = new FileReader();

        if (fr.Open(args[0]))
        {
            // Assume that an ASCII file is being read.
            System.Text.ASCIIEncoding Encoding = new System.Text.ASCIIEncoding();

            int bytesRead;
            do
            {
                bytesRead = fr.Read(buffer, 0, buffer.Length);
                string content = Encoding.GetString(buffer, 0, bytesRead);
                System.Console.Write("{0}", content);
            }
            while (bytesRead > 0);

            fr.Close();
            return 0;
        }
        else
        {
            System.Console.WriteLine("Failed to open requested file");
            return 1;
        }
    }
}

性能影響

這裏介紹兩個會對性能有較大影響的操做: 1)裝箱和拆箱 裝箱和取消裝箱都是須要大量運算的過程。對值類型進行裝箱時,必須建立一個全新的對象。此操做所需時間可比賦值操做長 20 倍。取消裝箱時,強制轉換過程所需時間可達賦值操做的四倍。 2)不要建立空的析構函數 不該使用空析構函數。若是類包含析構函數,Finalize 隊列中則會建立一個項。調用析構函數時,將調用垃圾回收器來處理該隊列。若是析構函數爲空,只會致使性能下降。

相關文章
相關標籤/搜索