寫在前面
系列文章
隱式類型
自動屬性
初始化器
匿名類
總結
上篇文章是本系列的小插曲,也是在項目中遇到,覺得有必要總結一下,就順手寫在了博客中,也希望能幫到一些朋友。本文將繼續介紹linq系列的基礎知識,隱式類型,自動屬性,初始化器,匿名類的相關概念,這些內容也許與linq相關也許不相關,但還是放一起總結吧,也算是復習了。部分內容通過反編譯的方式一探究竟。
Linq之Lambda表達式初步認識
Linq之Lambda進階
先看看Msdn上對隱式類型的簡單定義
從 Visual C# 3.0 開始,在方法范圍中聲明的變量可以具有隱式類型 var。 隱式類型的本地變量是強類型變量(就好像您已經聲明該類型一樣),但由編譯器確定類型。
一個例子
1 static void Main(string[] args) 2 { 3 //隱式類型 implicitly typed 4 var i = 1; 5 //顯示類型 explicitly typed 6 int j = 1; 7 Console.WriteLine(i.ToString()); 8 Console.WriteLine(j.ToString()); 9 Console.ReadKey(); 10 }
從上面msdn上的解釋,隱式類型本地變量是強類型的,由編譯器確定類型,那麼我們編譯一下該項目,然後使用ILspy反編譯查看該程序集,看看是不是將i定義為了int類型,以解心中的迷惑。
你會發現,在編譯之後,變量該是什麼類型就是什麼類型,感覺好牛逼的樣子。
那到底那些情況下可以用var?在不知道返回的到底是什麼類型的時候,var的用處非常大,還有就是如果返回值類型非常長,寫var也是比較輕松的,但有的時候你想真真切切看到到底是什麼類型的時候,這個時候就不要用var了。再看一個例子:
//該方式返回一個匿名類,這個時候不知道用什麼類型來接受,就用var var custQuery = from cust in customers where cust.City == "Phoenix" select new { cust.Name, cust.Phone };
如果要遍歷上面的結果,此時也必須是var,也就有了類似下面的代碼
1 foreach(var item in custQuery) 2 { 3 //邏輯代碼 4 }
首先看MSDN的說法
在 C# 3.0 和更高版本中,當屬性的訪問器中不需要其他邏輯時,自動實現的屬性可使屬性聲明更加簡潔。 客戶端代碼還可通過這些屬性創建對象。
這就話有點意思,“當屬性的訪問器中不需要其他邏輯時”,那這就話另外一個意思是,如果訪問器中需要其他邏輯時,就不使用自動屬性。
一個例子
如果有這樣一個Person類,對年齡加了限制,年齡不得小於0,不得大於100,此時如果使用自動屬性,邏輯無法添加了。如:
1 class Person 2 { 3 private int age; 4 public int Age 5 { 6 set 7 { 8 if (age > 0 && age <= 100) 9 { 10 age = value; 11 } 12 else 13 { 14 throw new Exception("年齡必須在1~100范圍內"); 15 } 16 } 17 get { return age; } 18 } 19 public string Name { set; get; } 20 }
那麼定義的自動屬性,編譯器又替咱們做了那些工作呢?反編譯一下看個究竟
看來編譯器為咱們做了很多工作。
最常用到的初始化器有對象初始化器和集合初始化器,數組初始化器。還以上面的Person類為例:
c#3.0之前的做法是
1 Person p = new Person(); 2 p.Name = "wolfy"; 3 p.Age = 23;
現在又多了一種新的玩法,對象初始化器,你現在可以這樣寫:
在寫完大括號後,只需要一個空格,所有的屬性就會智能提示出來,而且編寫一個就會少一個屬性,避免了多個屬性的混淆。
相比之前的做法
如果一個類的字段很多,由於基類object的屬性及方法的干擾,上下鍵的去找屬性,實在是太麻煩了。通過對象初始化器,你寫一個少一個,也避免出現了少為屬性賦值的情況了。
那麼對象初始化器,編譯器又為我們做了什麼?同樣還是反編譯一下看一個究竟
代碼:
1 Person p = new Person() { Name="wolfy", Age=23 };
反編譯查看的結果:
集合初始化器
代碼
1 List<Person> persons = new List<Person>() 2 { 3 new Person(){ Age=1, Name="張三"}, 4 new Person(){Age=2,Name="李四"} 5 };
反編譯查看的結果:
的確夠智能了,不能忍了,編譯器替你一個一個去Add了。
數組呢?
代碼
1 Person[] persons = new Person[]{ 2 new Person(){ Name="張三", Age=1}, 3 new Person(){ Name="李四" , Age=2} 4 };
看來內部並沒什麼變化。
同樣查看MSDN的定義
匿名類型提供了一種方便的方法,可用來將一組只讀屬性封裝到單個對象中,而無需首先顯式定義一個類型。 類型名由編譯器生成,並且不能在源代碼級使用。 每個屬性的類型由編譯器推斷。
匿名類型通常用在查詢表達式的 select 子句中,以便返回源序列中每個對象的屬性子集。
匿名類型包含一個或多個公共只讀屬性。 包含其他種類的類成員(如方法或事件)為無效。 用來初始化屬性的表達式不能為 null、匿名函數或指針類型。
有了匿名類類型可以這樣寫代碼
1 var v = new { age = 13, name = "wolfy" }; 2 Console.WriteLine(v.age + " " + v.name);
反編譯看一下IL
通過上面IL,也可以說明為什麼不能賦值為null,這樣編譯器就無法推斷它到底是什麼類型了。
匿名類的屬性是只讀的,無法修改
如果不為匿名類中的屬性指定名稱,則采用初始化這些屬性時所采用的屬性的名稱作為匿名類的屬性名稱,看一個例子
通常,當你使用匿名類型來初始化變量時,可以通過使用 var 將變量作為隱式鍵入的本地變來變量進行聲明。 類型名稱無法在變量聲明中給出,因為只有編譯器能訪問匿名類型的基礎名稱。
一個例子
1 //可通過將隱式鍵入的本地變量與隱式鍵入的數組相結合創建匿名鍵入的元素的數組 2 var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};
匿名類型是直接從對象派生的類類型,並且其無法強制轉換為除對象外的任意類型。 雖然你的應用程序不能訪問它,編譯器還是提供了每一個匿名類型的名稱。 從公共語言運行時的角度來看,匿名類型與任何其他引用類型沒有什麼不同。
如果程序集中的兩個或多個匿名對象初始值指定了屬性序列,這些屬性采用相同順序且具有相同的名稱和類型,則編譯器將對象視為相同類型的實例。 它們共享同一編譯器生成的類型信息。
無法將字段、屬性、時間或方法的返回類型聲明為具有匿名類型。 同樣,你不能將方法、屬性、構造函數或索引器的形參聲明為具有匿名類型。 要將匿名類型或包含匿名類型的集合作為參數傳遞給某一方法,可將參數作為類型對象進行聲明。 但是,這樣做會使強類型化作用無效。 如果必須存儲查詢結果或者必須將查詢結果傳遞到方法邊界外部,請考慮使用普通的命名結構或類而不是匿名類型。
由於匿名類型上的 Equals 和 GetHashCode 方法是根據方法屬性的 Equals 和 GetHashCode 定義的,因此僅當同一匿名類型的兩個實例的所有屬性都相等時,這兩個實例才相等。
本文通過反編譯的方式,學習了隱式類型,自動屬性,初始化器一級匿名類型的相關概念。常見的c#語法糖,在一起總結了。
參考文章
http://msdn.microsoft.com/zh-cn/library/bb383973.aspx
http://msdn.microsoft.com/zh-cn/library/bb384054.aspx
http://msdn.microsoft.com/zh-cn/library/bb397696.aspx