局部類型
C# 1.1中要求將類的全部代碼放在一個檔案中。而在C# 2.0允許將類或結構的定義和實現分開放在多個檔案中。通過使用新的partial關鍵字來標註分割,可以將類的一部分放在一個檔案中,而將另一個部分放在一個不同的檔案中。例如,可以將下面的代碼放到檔案MyClass1.cs中:
public partial class MyClass
{
public void Method1()
{}
}
在檔案MyClass2.cs中,可以插入下面的代碼:
public partial class MyClass
{
public void Method2()
{}
public int Number;
}
實際上,可以將任一特定的類分割成任意多的部分。局部類型支援可以用於類、結構和介面,但是不能包含局部枚舉定義。局部類型是一個非常有用的功能。有時,需要修改機器產生的檔案,例如Web服務用戶端封裝類。然而,當重建此封裝類時,對該檔案的修改將會被丟棄。通過使用局部類,可以將這些改變分開放在單獨的檔案中。在ASP.NET中可以將局部類用於code-beside編輯(從code-behind演變而來),單獨儲存頁面中機器產生的部分,而在Windows表單中使用局部類來儲存InitializeComponent方法的視覺化設計工具輸出以及成員控制項。通過使用局部類型,兩個或者更多的開發人員可以工作在同一個類型上,同時都可以從原始碼控制中籤出其檔案而不互相影響。
但是,如果多個不同的部分對同一個類做出了相互矛盾的定義會出現什麼樣的後果?答案很簡單。一個類(或一個結構)可能具有兩個不同的方面或性質:累積性的(accumulative)和非累積性的(non-accumulative)。累積性的方面是指類可以選擇添加它的各個部分,比如介面派生、屬性、索引器、方法和成員變數。例如,下面的代碼顯示了一個部分是如何添加介面派生和實現的:
public partial class MyClass
{}
public partial class MyClass : IMyInterface
{
public void Method1()
{}
public void Method2()
{}
}
非累積性的方面是指一個類型的所有部分都必須一致。無論這個類型是一個類還是一個結構,類型可見度(公用或內部)和基類都是非累積性的方面。例如,下面的代碼不能編譯,因為並非MyClass的所有部分都出現在基類中:
public class MyBase
{}
public class SomeOtherClass
{}
public partial class MyClass : MyBase
{}
public partial class MyClass : MyBase
{}
//Does not compile
public partial class MyClass : SomeOtherClass
{}
除了所有的部分都必須定義相同的非累積性部分以外,只有一個部分能夠重寫虛方法或抽象方法,並且只有一個部分能夠實現介面成員。
C# 2.0是這樣來支援局部類型的:當編譯器構建程式集時,它將來自多個檔案的同一類型的各個部分組合起來,並用中繼語言(Microsoft intermediate language, MSIL)將這些部分編譯成單一類型。產生的中繼語言中不含有哪一部分來自哪個檔案的記錄。正如在C# 1.1中一樣。另外值得注意的是,局部類型不能跨越程式集,並且通過忽略其定義中的partial限定符,一個類型可以拒絕包含其他部分。
因為編譯器所做的只是將各個部分累積,所以一個單獨的檔案可以包含多個部分,甚至是包含同一類型的多個部分,儘管這樣做的意義值得懷疑。
在C#中,開發人員通常根據檔案所包含的類來為檔案命名,這樣可以避免將多個類放在同一個檔案中。在使用局部類型時,建議在檔案名稱中指示此檔案包含哪個類型的哪些部分(例如MyClassP1.cs、MyClassP2.cs),或者採用其他一致的方式從類的名稱上指示源檔案的內容。例如,Windows表單設計人員將用於該表單的局部類的一部分存放在Form1.cs中,並將此檔案命名為Form1.Designer.cs。
局部類的另一個不利之處是,當開始接觸一個不熟悉的代碼時,所維護的類的各個部分可能遍布在整個項目的檔案中。在這種情況下,可以使用Visual Studio Class View,因為它可以將一個類型的所有部分積累起來展示給您,並允許通過單擊它的成員來導航各個不同的部分。導覽列也提供了這個功能。
匿名方法
C#支援用於調用一個或多個方法的委託(delegate)。委託提供運算子和方法來添加或刪除目標方法,它也可以在整個.NET架構中廣泛地用於事件、回調、非同步呼叫、多線程等。然而,僅僅為了使用一個委託,有時不得不建立一個類或方法。在這種情況下,不需要多個目標,並且調用的代碼通常相對較短而且簡單。在C# 2.0中,匿名方法是一個新功能,它允許定義一個由委託調用的匿名(也就是沒有名稱的)方法。例如,下面是一個常規SomeMethod方法的定義和委託調用:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = new SomeDelegate(SomeMethod);
del();
}
void SomeMethod()
{
MessageBox.Show("Hello");
}
}
可以用一個匿名方法來定義和實現這個方法:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = delegate()
{
MessageBox.Show("Hello");
};
del();
}
}
匿名方法被定義為內聯方法,而不是作為任何類的成員方法。此外,無法將方法屬性應用到一個匿名方法,並且匿名方法也不能定義泛型型別或添加泛型約束。
關於匿名方法有兩個地方值得注意:delegate保留關鍵字的重載使用和委託指派。稍後,將看到編譯器如何?一個匿名方法,而通過查看代碼,就可以瞭解編譯器如何推理所使用的委託的類型,執行個體化推理類型的新委派物件,將新的委託封裝到匿名方法中,並將其指派給匿名方法定義中使用的委託(前面的樣本中的del)。
匿名方法可以用在任何需要使用委託類型的地方。可以將匿名方法傳遞給任何方法,只要該方法接受適當的委託類型作為參數即可:
class SomeClass
{
delegate void SomeDelegate();
public void SomeMethod()
{
InvokeDelegate(delegate(){MessageBox.Show("Hello");});
}
void InvokeDelegate(SomeDelegate del)
{
del();
}
}
如果需要將一個匿名方法傳遞給一個接受抽象Delegate參數的方法,例如:
void InvokeDelegate(Delegate del);
則首先需要將匿名方法強制轉換為特定的委託類型。
下面是一個將匿名方法作為參數傳遞的具體的實用例子,它在沒有顯式定義ThreadStart委託或線程方法的情況下啟動一個新的線程:
public class MyClass
{
public void LauchThread()
{
Thread workerThread = new Thread(delegate()
{
MessageBox.Show("Hello");
});
workerThread.Start();
}
}
在前面的樣本中,匿名方法被當作線程方法來使用,這會導致訊息框從新線程中顯示出來。將參數傳遞到匿名方法。
當定義帶有參數的匿名方法時,應該在delegate關鍵字後面定義參數類型和名稱,就好像它是一個常規方法一樣。方法調用必須與它指派的委託的定義相匹配。當調用委託時,可以傳遞參數的值,與正常的委託調用完全一樣:
class SomeClass
{
delegate void SomeDelegate(string str);
public void InvokeMethod()
{
SomeDelegate del = delegate(string str)
{
MessageBox.Show(str);
};
del("Hello");
}
}
如果匿名方法沒有參數,則可以在delegate關鍵字後面使用一對空括弧:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = delegate()
{
MessageBox.Show("Hello");
};
del();
}
}
然而,如果將delegate關鍵字與後面的空括弧一起忽略,則定義一種特殊的匿名方法,它可以指派給具有任何調用的任何委託:
class SomeClass
{
delegate void SomeDelegate(string str);
public void InvokeMethod()
{
SomeDelegate del = delegate
{
MessageBox.Show("Hello");
};
del("Parameter is ignored");
}
}
很明顯,如果匿名方法並不依賴於任何參數,而且想要使用這種與委託調用無關的方法代碼,則只能使用這樣的文法。
注意,當調用委託時,仍然需要提供參數,因為編譯器為從委託簽名中推理的匿名方法產生無名參數,就好像編寫了下面的代碼(C#偽碼)一樣:
SomeDelegate del = delegate(string)
{
MessageBox.Show("Hello");
};
此外,不帶參數的匿名方法不能與帶參數的委託一起使用。
class SomeClass
{
string m_Space = " ";
delegate void SomeDelegate(string str);
public void InvokeMethod()
{
string msg = "Hello";
SomeDelegate del = delegate(string name)
{
MessageBox.Show(msg + m_Space + name);
};
del("Juval");
}
}
圖 7 匿名方法代碼中的局部變數
匿名方法可以使用任何類成員變數,並且它還可以使用定義在其包含方法範圍之內的任何局部變數。圖7對此進行了展示。一旦知道如何為一個匿名方法傳遞參數,也就可以很容易地定義匿名事件處理,8所示。
public class MyForm : Form
{
Button m_MyButton;
public MyForm()
{
InitializeComponent();
m_MyButton.Click += delegate(object sender,EventArgs args)
{
MessageBox.Show("Clicked");
};
}
void InitializeComponent()
{}
}
圖 8 匿名方法作為事件處理常式
因為+=運算子僅僅將一個委託的內部調用列表與另一個委託的內部調用列表串連起來,所以可以使用+=來添加一個匿名方法。注意,在匿名事件處理的情況下,不能使用-=運算子來刪除事件處理方法,除非將匿名方法作為處理常式加入,要這樣做,可以首先將匿名方法儲存為一個委託,然後通過事件註冊該委託。這樣,可以將-=運算子作用於儲存的委託來取消註冊。