委託推理
C#編譯器從匿名方法指派推理哪個委託類型將要執行個體化的能力是一個非常重要的功能。實際上,它還提供了另一個叫做委託推理的C# 2.0功能。委託推理允許直接給委託變數指派方法名,而不需要先使用委派物件封裝它。例如下面的C# 1.1代碼:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = new SomeDelegate(SomeMethod);
del();
}
void SomeMethod()
{}
}
現在,可以編寫下面的代碼來代替前面的代碼片斷:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = SomeMethod;
del();
}
void SomeMethod()
{}
}
當將一個方法名指派給委託時,編譯器首先推理該委託的類型。然後,編譯器根據此名稱檢驗是否存在一個方法,並且它的調用是否與推理的委託類型相匹配。最後,編譯器建立一個推理委託類型的新對象,以便封裝此方法,並將其指派給該委託。如果該類型是一個具體的委託類型(即除了抽象類別型Delegate之外的其他類型),則編譯器只能推理委託類型。委託推理的確是一個非常有用的功能,它可以使代碼變得簡練而優雅。
作為C# 2.0中的慣例,開發人員將會使用委託推理,而不是以前的委託執行個體化方法。例如,下面的代碼說明了如何在不顯式地建立一個ThreadStart委託的情況下啟動一個新的線程:
public class MyClass
{
void ThreadMethod()
{}
public void LauchThread()
{
Thread workerThread = new Thread(ThreadMethod);
workerThread.Start();
}
}
當啟動一個非同步呼叫並提供一個完整的回調方法時,可以使用一對委託推理,11所示。首先,指定非同步呼叫的方法名來非同步呼叫一個匹配的委託。然後調用BeginInvoke,提供完整的回調方法名而不是AsyncCallback類型的委託。
class SomeClass
{
delegate void SomeDelegate(string str);
public void InvokeMethodAsync()
{
SomeDelegate del = SomeMethod;
del.BeginInvoke("Hello",OnAsyncCallBack,null);
}
void SomeMethod(string str)
{
MessageBox.Show(str);
}
void OnAsyncCallBack(IAsyncResult asyncResult)
{}
}
圖 11 使用委託推理
屬性和索引可見度
C# 2.0允許為屬性或索引器的get和set訪問器指定不同的可見度。例如,在通常情況下,可能想將get訪問器公開為public,而把set訪問器公開為protected。為此,可以為set關鍵字添加protected可見度限定符。類似地,可以將索引器的set方法定義為protected(請參見圖12)。
public class MyClass
{
public string this[int index]
{
get
{
return m_Names[index];
}
protected set
{
m_Names[index] = value;
}
}
string[] m_Names;
//Rest of the class
}
圖 12 Public Get 與 Protected Set
當使用屬性可見度時有幾項規定。首先,應用在set或get上的可見度限定詞只能是此屬性本身可見度的嚴格子集。換句話說,如果此屬性是public,那麼就可以指定internal、protected、protected internal、private。如果此屬性可見度是protected,就不能將get或set公開為public。此外,只能分別為get或set指定可見度,而不能同時為它們指定可見度。靜態類有些類只有靜態方法或靜態成員(靜態類),這是非常常見的。在這種情況下,執行個體化這些類的對象沒有意義。例如,Monitor類或類工廠(例如.NET架構1.1中的Activator類)都是靜態類。在C# 1.1中,如果想要阻止開發人員執行個體化類的對象,可以只提供一個私人的預設建構函式。如果沒有任何公用的建構函式,就不可以執行個體化類的對象:
public class MyClassFactory
{
private MyClassFactory()
{}
static public object CreateObject()
{}
}
然而,因為C#編譯器仍然允許添加執行個體成員(儘管可能從來都不使用它們),所以是否在類中只定義靜態成員完全由您決定。C# 2.0通過允許將類限定為static來支援靜態類:
public static class MyClassFactory
{
static public T CreateObject()
{}
}
C# 2.0編譯器不允許將一個非靜態成員添加到一個靜態類中,也不允許建立此靜態類的執行個體,就好像它是一個抽象類別一樣。此外,不能從一個靜態類派生子類。這就如同編譯器在靜態類定義中加入了abstract和sealed一樣。注意,可以定義靜態類而不能定義靜態結構,並且可以添加靜態建構函式。
全域命名空間限定符
很可能有這樣一個嵌套的命名空間,它的名稱與一些其他的全域命名空間相匹配。在這種情況下,C# 1.1編譯器在解析命名空間引用時會出現問題。請考慮下例:
namespace MyApp
{
namespace System
{
class MyClass
{
public void MyMethod()
{
System.Diagnostics.Trace.WriteLine("It Works!");
}
}
}
}
在C# 1.1中,調用Trace類會產生編譯錯誤(沒有全域命名空間限定符::)。出現這種錯誤的原因在於,當編譯器嘗試解析對System命名空間的引用時,它使用直接包含範圍,此範圍包含System命名空間但不包含Diagnostics命名空間。C# 2.0允許您使用全域命名空間限定符::來表示編譯器應該在全域範圍內進行搜尋。可以將::限定符應用於命名空間和類型,13所示。
namespace MyApp
{
class MyClass
{
public void MyMethod()
{
::MyClass obj = new ::MyClass();
obj.MyMethod(); // Traces "Hello" instead of recursion
}
}
}
public class MyClass
{
public void MyMethod()
{
Trace.WriteLine("Hello");
}
}
圖 13 使用全域命名空間限定符
內聯警告
C# 1.1允許使用項目設定或者通過向編譯器發布命令列參數來禁止特殊的編譯器警告。但是這是一個全域性的取消,這樣做會取消一些仍然需要的警告。C# 2.0允許使用#pragma警告指令顯式地取消和恢複編譯器警告:
// Disable 'field never used' warning
#pragma warning disable 169
public class MyClass
{
int m_Number;
}
#pragma warning restore 169
在產品代碼中通常並不鼓勵禁止警告。禁止警告只是為了進行某些分析,比如,當您嘗試隔離一個問題時,或者當您設計代碼並且想要得到代碼合適的初始結構而不必先行對其加以完善時。而在所有其他的情況下,都要避免取消編譯器警告。注意,不能通過編程的方式來重寫項目設定,這意味著不能使用pragma警告指令來恢複全域取消的警告。
小結
本文所提到的C# 2.0中的一些新功能是專門的解決方案,旨在處理特定的問題,同時可以簡化整體編程模型。如果關注工作效率和品質,就需要讓編譯器產生儘可能多的實現,減少重複性的編程任務,使最後得到的代碼簡潔易讀。新的功能帶來的正是這些特色,它們象徵著C#時代的到來,將會使C#成為服務於.NET專業開發人員的優秀工具