C#復習筆記(4)--C#3:革新寫代碼的方式(用智能的編譯器來防錯)
用智能的編譯器來防錯
本章的主要內容:
- 自動實現的屬性:編寫由字段直接支持的簡單屬性, 不再顯得臃腫不堪;
- 隱式類型的局部變量:根據初始值推斷類型,簡化局部變量的聲明;
- 對象和集合初始化程序:用一個表達式就能創建和初始化對象;
- 隱式類型的數組:根據內容推斷數組的類型,從而簡化數組的創建過程;
- 匿名類型:允許創建新的臨時類型來包含簡單的屬性;
自動實現的屬性
這個特性簡單的我都不想描述,但是為了保持內容的完整性,放一張圖:
和匿名方法還有叠代器一樣,它在編譯器的幫助下會生成一個後備字段。
自動實現的屬性是賦值和取值方法都是共有的,當然你還可以繼續使用C#2私有賦值方法。
在實現自己的結構(struct)時,所有構造函數都必須顯式的調用一下無參的構造函數,只有這樣,編譯器才知道所有的字段都被明確賦值了。因為這裏有個類型初始化的順序:
類或結構在初始化時的執行順序,依次如下:
1: 子類靜態變量
2: 子類靜態構造函數
3: 子類非靜態變量
4: 父類靜態變量
5: 父類靜態構造函數
6: 父類非靜態變量
7: 父類構造函數
8: 子類構造函數
可以看到除了構造函數以外其他東西都是先初始化本身在初始化基類的。
隱式類型的局部變量
首先需要說明一點的時隱式類型只能用於局部變量,不能用於字段變量。
第二點是,C#仍然是一門靜態的語言,只是要求編譯器為你來推斷變量的類型,在編譯時仍然是類型靜態的。
用var關鍵字來聲明一個隱式類型的局部變量。
小小的總結:不是在所有情況下都能為所有變量使用隱式類型, 只有在以下情況下才能用它:
- 被聲明的變量是一個局部變量, 而不是靜態字段和實例字段;
- 變量在聲明的同時被初始化;
- 初始化表達式不是方法組, 也不是匿名函數( 如果不進行強制類型轉換);
- 初始化表達式不是 null;
- 語句中只聲明了一個變量;
- 你希望變量擁有的類型是初始化表達式的編譯時類型;
- 初始化表達式不包含正在聲明的變量 。
隱式類型的局部變量也有一些不好的地方,有的時候你不得不仔細的判斷它的類型。例如:
- var a = 2147483647;
- var b = 2147483648;
- var c = 4294967295;
- var d = 4294967296;
- var e = 9223372036854775807;
- var f = 9223372036854775808;
上面的這些變量的類型都不好在第一時間就判斷出來。但是,有的時候你不得不用,比如要返回一個匿名的類型,只能這樣寫:var a=new {name="pangjianxin",age=10};這樣的表達式你要用什麽類型來引用呢?
簡化的初始化
直接上代碼。
public class Person { public string Name { get; } public int Age { get; set; } public List<Person> Persons { get; } = new List<Person>(); public Location Location { get; } = new Location(); public Person() { } public Person(string name) { this.Name = name; } } public class Location { public string City { get; set; } public string Street { get; set; } }
上面定義兩個類,一個Person類,一個Location類。Person類中維護兩個自動屬性Name和Age,另外,還維護了兩個只讀屬性Persons和Location。還有一個無參的構造函數和一個有參數的構造函數。
前面已經說過,類會在靜態的和非靜態的字段初始化後才會執行構造函數,屬性本質上來說是一對get/set方法,不存在初始化。
看一下調用情況:
static void Main(string[] args) { Person p = new Person("pangianxin") { Age = 18, Location = { City = "baotou", Street = "gangtiedajie" } }; Console.WriteLine(p.Location.City); //p.Location=new Location();無法對Location進行初始化,因為他是只讀的。
p.Location.City = "baotou ";
p.Location.Street = "gangtiedajie ";
Console.ReadKey();
}
上面使用了對象初始化程序來對對象進行初始化。
首先註意到的是p.Location是一個只讀的屬性。我們不能直接該給屬性進行賦值,但是可以在取到這個屬性的引用後,再對其進行賦值,在C#語言規範裏面,這個叫做“設置一個嵌入對象的屬性”。就是設置屬性的屬性。這樣卻沒有了限制。
第二點是Location = { City = "baotou", Street = "gangtiedajie" }這句。編譯器發現等號右側的是另一個對象初始化程序, 所以會適當地將屬性應用到嵌入對象。
集合初始化程序
var names = new List { "Holly", "Jon", "Tom", "Robin", "William" };
就是這樣。
同樣是編譯器在後臺調用add方法來將元素add進集合。
集合初始化程序必須要遵循以下兩點:
- 實現IEnumerale
- 具有add方法
對於第一點來說,要求實現IEnumerable是合理的,因為編譯器必須得知道是某種意義上的集合。對於第二點,因為編譯器會在後臺調用add方法來存放元素,所以,你初始化的這個集合必須得保證有這個add方法。
隱式類型的數組
string[] names = {"Holly", "Jon", "Tom", "Robin", "William"};
這種方式看起來很簡潔,但是不能將大括號裏面的東西直接傳遞給方法:MyMethod({" Holly", "Jon", "Tom", "Robin", "William"});這樣會報錯。要這樣:MyMethod( new string[] {"Holly", "Jon", "Tom", "Robin", "William"});
匿名類型
這個玩意兒才是今天的重點。
匿名類型在與更高級的特性結合起來才會更有用。
用這個東西初始化的類型也只能用var來承接了:var myInfo=new {name="pangjianxin“,age=19};當然。除了object以外。
如果兩個匿名對象初始化程序包含相同數量的屬性, 且這些屬性具有相同的名稱和類型, 而且以相同的順序出現, 就認為它們是同一個類型。
static void Main(string[] args) { var persons = new[] { new { name = "pangjianxin", age = 19 }, new { name = "pangjianxin", age = 19 }, new { name = "pangjianxin", age = 19 }, new { name = "pangjianxin", age = 19 }, new { name = "pangjianxin", age = 19 } }; Console.ReadKey(); }
如果上面的匿名類型的類型不一致,比如吧其中一個的屬性的順序顛倒,額外增加一個屬性等等,編譯器會報錯:”找不到隱式類型數組的最佳類型“。
編譯器在後臺為匿名類型生成了一個泛型的類型來幫助匿名類型進行初始化,這個泛型類被放到了一個單獨的程序集中。它在後臺生成的類名超級變態:
它將匿名類型中的name和age的類型作為泛型類型的類型參數,然後,main方法中變成了這樣:
編譯器厲害吧?
返回來看一下匿名類型具有哪些成員:
首先它是繼承了object。廢話
它有後備字段和只讀的屬性
有獲取所有初始值的構造函數。
重寫了object的Equals、GetHashCode、ToString
由於所有屬性都是只讀的,所以只要這些屬性是不易變的, 那麽匿名類型就是不易變的。 這就為你提供了“ 不易變” 這一特性所具有全部常規性的優勢—— 能放心向方法傳遞值, 不用害怕這些值被改變; 能在線程之間共享數據, 等等。
投影初始化程序
var person = new {name = "pangjianxin", age = 19};
var anotherPerson = new {name = person.name, isAdult = (person.age > 18)};
anotherPerson利用person的屬性形成了一個新的匿名類型。這就是投影初始化程序。
不過匿名類型最大的用處在於linq中。利用select或selectmany等操作可以從橫向的縮小要查找的範圍。
C#復習筆記(4)--C#3:革新寫代碼的方式(用智能的編譯器來防錯)