小結(jié)
背景
2001 年年底,ECMA 將 C# 編程語言批準(zhǔn)為一項(xiàng)標(biāo)準(zhǔn) (ECMA-334)。為了與 Microsoft 在 C# 和公共語言接口 (CLI) 標(biāo)準(zhǔn)化進(jìn)程方面的舉措保持一致,Microsoft 遵循 ECMA C# 標(biāo)準(zhǔn)的精神和文字規(guī)范對 C# 編譯器進(jìn)行了幾處小的改動。另外,Microsoft 在遵循 C# 標(biāo)準(zhǔn)規(guī)范的同時對 C# 實(shí)現(xiàn)作了一些額外的小改動,并更正了 C# 程序員遇到的一些編譯器問題和錯誤。其中的每處改動都可能導(dǎo)致使用 Visual C# .NET 2002 版編譯器編寫的代碼在用于 Visual C# .NET 2003 之前必須進(jìn)行修改。
C# 語言的新功能
Visual C# .NET 2003 版的 C# 語言中添加了兩個新功能。第一,編譯器現(xiàn)在支持 #line hidden 預(yù)處理器指令。#line hidden 指令主要用于源代碼生成器,它通知編譯器忽略緊跟在 #line hidden 指令后面的所有代碼行的調(diào)試程序信息,直到遇到下一個 #line 指令為止(該 #line 指令的調(diào)試程序信息也一并被忽略),這里假設(shè)它們中間不會立即碰到下一個 #line hidden 預(yù)處理指令。在下面的示例中,編譯器生成了 IL 代碼,其中的 WriteLine 語句不包含調(diào)試信息。這樣,調(diào)試應(yīng)用程序的程序員將無法查看“隱藏”的代碼并檢查其中的內(nèi)容:
public class Customer
{
public static void Main()
{
MyClass c = new MyClass();
c.ExecuteCommand();
#line hidden
Console.WriteLine("顯示一些文字");
Console.WriteLine("顯示一些文字");
Console.WriteLine("顯示一些文字");
#line
c.ProcessCommand();
c.Close();
}
}
然而,#line hidden
指令并不隱藏編譯器錯誤。當(dāng)然,編譯器仍然將代碼編譯到 IL 中,且代碼仍舊執(zhí)行;編譯器只是禁止調(diào)試程序進(jìn)入它的內(nèi)容。
第二個 C# 新功能涉及 XML 注釋,是根據(jù) ECMA 標(biāo)準(zhǔn)添加的。C# 現(xiàn)在支持在使用“斜線和星號”符號(/*
和 */
)編寫的多行注釋中添加 XML 注釋。下面的 XML 注釋在 2003 版的 C# 編譯器中是合法的:
/**
<summary>這是
注釋
</summary>
*/
此外,出于完整性的考慮(但實(shí)際上絕不推薦),程序員可以混合并匹配注釋樣式,同時仍然能夠編寫出有效的 XML 注釋代碼。這樣,下面的這個注釋聲明現(xiàn)在也是合法的:
/**
<summary>這是
注釋
*/
/// </summary>
實(shí)現(xiàn)的改變
2003 版的 C# 編譯器和 2002 版也有微小的區(qū)別。在有些情況下,這些區(qū)別可能會導(dǎo)致代碼無法編譯,或?qū)е缕溥\(yùn)行方式與應(yīng)執(zhí)行的方式大相徑庭。
“Foreach”語句的改變
現(xiàn)在,foreach 語句可以動態(tài)地檢查它所迭代的數(shù)據(jù)結(jié)構(gòu)中是否存在 IDisposable 接口。以前,編譯器從不動態(tài)地檢查 Idisposable 接口是否存在,除非從 GetEnumerator 返回的類型已實(shí)現(xiàn)了 IEnumerator 接口。然而,如果此類型對于實(shí)現(xiàn) Idisposable 是靜態(tài)已知的,則編譯器將一直調(diào)用 Dispose。換句話說,如果迭代程序類型實(shí)現(xiàn)了枚舉器設(shè)計(jì)模式,但沒有專門實(shí)現(xiàn) IEnumerator 接口,編譯器就不會調(diào)用 Dispose 方法,除非 iterator 類型對于實(shí)現(xiàn) IDisposable 接口是靜態(tài)已知的。
現(xiàn)在,編譯器在檢測是否存在 IDisposable 接口時,無論迭代程序類型是否實(shí)現(xiàn) IEnumerator,都將調(diào)用 Dispose 方法(如果已實(shí)現(xiàn))。在下面的示例中,Visual C# .NET 2002 編譯器未調(diào)用 Dispose 方法,但 Visual C# .NET 2003 編譯器調(diào)用了該方法:
abstract class Base
{
public int Current { get; }
public bool MoveNext();
}
class Derived: Base, IDisposable
{
// Base 和 IDisposable 的實(shí)現(xiàn)
}
class MyClass
{
public Base GetEnumerator()
{
return new Derived();
}
}
當(dāng) foreach 語句在某個對象集合中使用迭代時,它將執(zhí)行 GetEnumerator 方法并接收轉(zhuǎn)換為 Base 類型的 Derived 實(shí)例作為它的迭代程序類型。當(dāng)然,Base 類型無需為了調(diào)用它的 Current 和 MoveNext 方法而實(shí)現(xiàn) Ienumerator 接口。在早期編譯器中,Derived 類型的 Dispose 方法不被調(diào)用,因?yàn)樗粚?shí)現(xiàn) IEnumerator,并且類 Base 對于實(shí)現(xiàn) Idisposable 不是靜態(tài)已知的。在新的編譯器中,Dispose 方法被調(diào)用,因?yàn)榫幾g器在所有 foreach 語句的迭代程序類型中檢查是否存在 Idisposable 接口。由于 GetEnumerator 調(diào)用的結(jié)果是一個轉(zhuǎn)換為 Base 類型的 Derived 類型,并且由于 Derived 類型實(shí)現(xiàn) Idisposable 接口,因此編譯器動態(tài)檢查 Idisposable 接口是否存在,會導(dǎo)致對 Dispose 方法的調(diào)用。
屬性聲明的改變
ECMA C# 標(biāo)準(zhǔn)明確禁止為相應(yīng)的屬性創(chuàng)建獲取和設(shè)置函數(shù)。實(shí)際上,C# 編譯器將屬性聲明轉(zhuǎn)換為獲取和設(shè)置函數(shù),以便不支持屬性的語言也可以訪問數(shù)據(jù)。因此,下面的代碼是無效的,因?yàn)榫幾g器會產(chǎn)生 get_Prop 和 set_Prop 方法,而這兩個方法與用戶聲明的方法發(fā)生沖突:
public class MyClass
{
public int Prop
{
get
{
}
set
{
}
}
// 現(xiàn)在屬于非法函數(shù)
public int get_Prop()
{
}
// 現(xiàn)在屬于非法函數(shù)
public void set_Prop(int val)
{
}
}
以前,C# 編譯器允許創(chuàng)建此類函數(shù),顯然這是一個軟件錯誤。2003 版的 C# 編譯器糾正了這個錯誤。
作為此編譯器錯誤糾正的必然結(jié)果,C# 編譯器將不再允許顯式創(chuàng)建生成屬性的獲取和設(shè)置函數(shù)(如果將屬性定義為接口實(shí)現(xiàn)的結(jié)果)。在下面的示例中,2003 版的 C# 編譯器不再允許在 Derived 類中顯式實(shí)現(xiàn) IMyInterface.get_Prop 和 IMyInterface.set_Prop 方法:
interface IMyInterface
{
public int Prop { get; set; }
}
public class Derived : IMyInterface
{
public int Prop
{
get
{
}
set
{
}
}
// 非法
public int IMyInterface.get_Prop()
{
}
// 非法
public void IMyInterface.set_Prop(int val)
{
}
}
其他改變
C# 編譯器的早期版本允許不兼容地使用屬性。2003 版的 C# 編譯器已經(jīng)糾正了這些用法,因此更符合 ECMA 規(guī)范。首先是對 C# 編譯器進(jìn)行了糾正,不允許在其參數(shù)列表中使用未在屬性類聲明中聲明為 public 的命名參數(shù)。例如,如果某個 AuthorAttribute 類是使用名為 authorName 的私有字段創(chuàng)建的,則下面的語句在 C# 編譯器的早期版本中是允許的,但在 C# 2003 編譯器中卻會導(dǎo)致錯誤:
[Author(authorName="microsoftuser")]
public class MyClass
{
}
第二,ObsoleteAttribute 現(xiàn)在可以應(yīng)用到運(yùn)算符,這樣程序員就可以使重載的運(yùn)算符函數(shù)失效。最后,編譯器以前對于無法識別的屬性位置常常生成一個錯誤,而現(xiàn)在則根據(jù) ECMA C# 規(guī)范的要求只生成一個警告。
另外,C# 編譯器以前接受用戶定義的移位運(yùn)算符參數(shù)(<< 和 >>),而根據(jù) ECMA C# 規(guī)范,這些參數(shù)是無效的。例如,移位運(yùn)算符以前可以按以下方式進(jìn)行聲明,即將封裝類的類型聲明為第二個操作數(shù):
public class MyClass
{
public static MyClass operator <<(int I, MyClass c)
{
}
public static void Main()
{
}
}
按照規(guī)范,如果向左移位運(yùn)算符被重載,則二進(jìn)制運(yùn)算符的操作數(shù)列表中的第一個參數(shù)必須為封裝類型。同樣,如果向右移位運(yùn)算符被重載,則二進(jìn)制運(yùn)算符的操作數(shù)列表中的第二個參數(shù)必須為封裝類型。下面的代碼示例演示了向左移位運(yùn)算符的正確聲明方式:
public class MyClass
{
public static MyClass operator <<( MyClass c, int i)
{
}
public static void Main()
{
}
}
最后,還加入了幾個用于糾正編譯器錯誤的修復(fù),包括: