標籤:pos 有用 oid 放心 投影 string erp 元素 局部變數
用智能的編譯器來防錯
本章的主要內容:
- 自動實作屬性:編寫由欄位直接支援的簡單屬性, 不再顯得臃腫不堪;
- 隱式類型的局部變數:根據初始值推斷類型,簡化局部變數的聲明;
- 對象和集合初始化程式:用一個運算式就能建立和初始化對象;
- 隱式類型的數組:根據內容推斷數組的類型,從而簡化數組的建立過程;
- 匿名型別:允許建立新的臨時類型來包含簡單的屬性;
自動實作屬性
這個特性簡單的我都不想描述,但是為了保持內容的完整性,放一張圖:
和匿名方法還有迭代器一樣,它在編譯器的協助下會產生一個後備欄位。
自動實作屬性是賦值和取值方法都是共有的,當然你還可以繼續使用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進集合。
集合初始化程式必須要遵循以下兩點:
對於第一點來說,要求實現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:革新寫代碼的方式(用智能的編譯器來防錯)