前面介紹了《C# 3.0 新特性》,對其注意事項沒有過多的介紹,在這補充一下,回顧一下.NET 3.X的新特性。
自動屬性(Automatic Properties)
不妨稱自動屬性之前的屬性為傳統屬性。自動屬性簡化了語法,但也失掉了屬性設置獲取時進行操作的功能,也無法設置初始值。
若想只讀或只寫可在set或get前加上訪問修飾符,設置的訪問修飾符必須比屬性本身的可訪問性低,並且不能同時設置get和set的訪問修飾符;internal和protected存在交集,因此不能同時設置屬性和get或set分別為internal和protected。
下面的代碼演示自動屬性和傳統屬性:
public class Test1
{
public int Int0 { get; set; }
public int Int1 { private get; set; }
private int int2;
private int int3;
public int Int2
{
get { return int2; }
set { int2 = value; }
}
public int Int3
{
get { return int3; }
}
}
編譯後用ILDasm查看如圖:
如自動屬性Int1生成了私有字段"<Int1>k__BackingField",盡管在C#裡這不是合法的標識符,但在IL裡是合法的。
生成了兩個方法:set_Int1(int32 'value')和get_Int1(),由於上面Int1的set設置器聲明為私有的,因此set_Int1(int32 'value')為私有方法。
而相對應的傳統的屬性如果編碼只有set或get,則get_XXX或set_XXX方法是不存在的。
如果在代碼裡寫入具有相同簽名的get_XXX或set_XXX方法編譯會出錯:"Type 'CS30NEW.Test1' already reserves a member called 'get_Int2' with the same parameter types"
屬性在IL用方法來實現的目的是跨語言,有些語言不支持屬性,可以使用相應的方法來實現相同的操作。IL生成的get_XXX或set_XXX盡管是公開的,但C#不允許顯式訪問這些方法,如果訪問這些方法編譯會出錯:"'CS30NEW.Test1.Int2.get': cannot explicitly call operator or accessor"。
隱含類型局部變量(Local Variable Type Inference)
如定義一個局部整型數組時:
int[] a = new int[10];
賦值符號後面已經知道是數組類型了賦值符號前面還要指明是數組類型,是不是顯得特別傻。
隱含類型可以這樣定義:
var a = new int[10];
a.GetType().ToString()輸出可以看到a為System.Int32[]類型。
貌似跟javascript的var很像,其實差別很大。javascript為弱類型語言,定義變量後可以對其任意賦值,而C#中用var定義局部變量有幾點注意事項:
顧名思義,var只能定義局部變量。
var聲明的同時必須初始化。
初始化時變量的類型已經確定,如果對其賦值其它類型編譯會出錯,提示類型不匹配。
var變量不能用於方法的形參。
在編譯時變量的類型已經確定,這也是編譯器做的工作。
對象初始化器(Object Initializers)
如有以下對象:
public class Test1
{
public string String1 { get; set; }
public int Int1;
public Test1() { }
public Test1(int i) { Int1 = i; }
}
構造實例對其初始化。var t0 = new Test1 { String1 = "http://xianfen.net", Int1 = 0 };
var t1 = new Test1(0) { String1 = "http://xianfen.net" };
IL內部對其初始化首先調用無參構造方法,然後調用的是屬性的set或設置公共字段來實現的。
因此使用省略構造方法的對象初始化器是必須含有無參構造方法。
對象初始化器也可以顯式調用構造方法,執行順序是構造方法優先執行。
集合初始化器(Collection Initializers)
集合初始化器可以初始化List<T>等集合。
如下示例:
public class Test1
{
public string String1 { get; set; }
public int Int1 { get; set; }
}
List<Test1> l = new List<Test1>
{
new Test1{ String1 = "http://xianfen.net", Int1 = 0 },
new Test1{ String1 = "http://www.xianfen.net", Int1 = 1 },
new Test1{ String1 = "http://www.bianceng.cn", Int1 = 2 }
};
IL代碼:
.locals init ([0] class [mscorlib]System.Collections.Generic.List`1<class CS30NEW.Test1> l,
[1] class [mscorlib]System.Collections.Generic.List`1<class CS30NEW.Test1> '<>g__initLocal0',
[2] class CS30NEW.Test1 '<>g__initLocal1',
[3] class CS30NEW.Test1 '<>g__initLocal2',
[4] class CS30NEW.Test1 '<>g__initLocal3')
從IL代碼可以看出,編譯器生成了局部變量,然後添加到集合中。
匿名類型(Anonymous Types)
匿名類型是最有爭議的特性,說白了就像雞肋,因為使用匿名類型有很多限制。
示例:
var t3 = new { String1 = "http://xianfen.net", Int1 = 0 };
其類型為:
t3.GetType().ToString()結果是:<>f__AnonymousType0`2[System.String,System.Int32]
檢查IL可看到:
編譯器生成了一些方法、屬性、字段。
其類型不可見(使用反射可使其類型可見),使用受到諸多限制。
如果改變初始化字段的順序,將生成新的類型
如:
var t4 = new { Int1 = 0, String1 = "http://xianfen.net" };
則t4.GetType().ToString()結果是:<>f__AnonymousType1`2[System.Int32,System.String]
當然改變屬性類型、數量肯定會生成新的類型。
但設置相同屬性但不同的值生成的類型相同。
var t3 = new { String1 = "http://xianfen.net", Int1 = 0 };
var t4 = new { String1 = "http://www.xianfen.net", Int1 = 10 };
t3.GetType()==t4.GetType()值為true。
匿名類型的示例在聲明周期中的Hash值是穩定不變的,也就是示例初始化後其內容不會改變。
如果試圖改變其值,編譯會出錯:"Property or indexer 'AnonymousType#1.String1' cannot be assigned to -- it is read only"。
匿名類型不可見,那麼如何使用方法返回匿名類型?好在.NET 2.0以後版本提供了泛型機制。
定義泛型方法:
public static T ReturnT<T>(Func<T> getType)
{
return getType();
}
調用方法:
var t3 = ReturnT(delegate { return new { String1 = "http://xianfen.net", Int1 = 0 }; });
t3.GetType().ToString()結果為:<>f__AnonymousType0`2[System.String,System.Int32]
貌似生成的類型有一定的規律,格式為:"<>f__AnonymousType匿名類型的序號`匿名類型屬性或字段或構造方法參數的數目",但無法精確確定接下來生成的匿名類型的名稱。
Lambda表達式(Lambda Expressions)
Lambda表達式應該是匿名方法的升級,更靈活,在接受委托的地方使用及其方便。
語法為:
(參數簽名列表)=>{操作}
等價於方法:
[其他修飾] 方法名(方法參數簽名列表){方法實現}
Func<string> f = () => { return "abc"; };
Console.Write(f());
輸出:abc
Func<[...,]T> 最後一個參數為返回值類型,其他的為方法參數類型,相當於:T function([...]){return T的實例;}
Action<[T,...]> T為參數類型,相當於void function([T,...]){}
Predicate<[T,...]> T為參數類型,相當於 bool function([T,...]){return true或false;}
以上是系統定義的泛型委托,這些委托跟Lambda表達式結合,靈活的實現各種功能。
上面的代碼檢查IL可看到生成了以下方法:
.method private hidebysig static string '<Main>b__0'() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
// Code size 11 (0xb)
.maxstack 1
.locals init ([0] string CS$1$0000)
IL_0000: nop
IL_0001: ldstr "abc"
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
} // end of method Program::'<Main>b__0'
擴展方法(Extension Methods)
有點類似於JavaScript的prototype。
在不改變原類型的基礎上擴展原類型。
當擴展方法的方法簽名與原類型的方法沖突時,原類型自身方法優先。
擴展方法不能訪問原類型的受保護的字段等等。
擴展方法可以對類及接口進行擴展。
.NET 3.X基礎代碼仍然是.NET 2.0的代碼,用擴展方法對其擴展了很多功能,如對List<T>的擴展增加了很多功能。
示例:
public class Test1
{
public string String1 { set; get; }
public int Int1 { set; get; }
}
public static class Test2
{
/*
* 實現擴展方法的類及擴展的方法都必須是靜態的,待擴展的類作為參數,前面加this關鍵字
*/
public static string AddDefaultPage(this Test1 t)
{
return t.String1 + "/Default.aspx";
}
}
var t4 = new Test1 { String1 = "http://xianfen.net", Int1 = 0 };
t4.AddDefaultPage();
IL對擴展方法的聲明及調用
.method public hidebysig static string AddDefaultPage(class CS30NEW.Test1 t) cil managed
call string CS30NEW.Test2::AddDefaultPage(class CS30NEW.Test1)
查詢語法(Query Syntax)
查詢語法是Linq的核心,後面詳細介紹。
這些新特性對CLR及IL相對與.NET2.0沒有任何改變,編譯器替開發人員做了相當多的工作,大大減少了編碼量,以便使開發人員專注與邏輯的實現,難怪安裝了.NET 3.X,運行Visual studio 2008命令提示行的clrver命令,顯示的是2.0.XXX。