C#學習筆記四: C#3.0自動屬性&匿名屬性及擴展方法


前言

這一章算是看這本書最大的收獲了, Lambda表達式讓人用着屢試不爽, C#3.0可謂顛覆了我們的代碼編寫風格. 因為Lambda所需篇幅挺大, 所以先總結C#3.0智能編譯器給我們帶來的諸多好處, 下一遍會單獨介紹Lambda表達式. 這篇主要包括的內容有: 自動屬性,隱式類型,對象集合初始化,匿名類型,擴展方法.

下面一起來看下C#3.0 所帶來的變化吧.

1,自動實現的屬性
在C#3.0之前, 定義屬性時一般會像下面這樣去編寫代碼:

 1 class Person
 2 {
 3     //定義私有字段
 4     private string name;
 5 
 6     //定義可讀寫屬性
 7     public string Name
 8     {
 9         get{return name;}
10         set{name = value;}
11     }
12 
13     private int age;
14     //定義只讀屬性
15     public int Age
16     {
17         get{return age;}
18     }
19 }
View Code

PS: 這里有一個快捷鍵: private string name; 選中name 然后Ctrl + R + E 就可以快捷生成Name屬性.

C#3.0之后, 對於不需要額外驗證的屬性(需要額外驗證的屬性還是必須采用之前的方式來定義), 我們可以使用自動實現的特性來對屬性的定義進行簡化, 此時不再需額外定義一個私有字段了.代碼如下:

1 class Person
2 {
3     //使用自動實現的屬性來定義屬性
4     //定義可讀寫屬性
5     public string Name{get; set;}
6     //定義只讀屬性
7     public int Age{get; private set;}
8 }
View Code

PS: 這里也有一個快捷鍵: 打出prop 然后點擊兩下Tab鍵就可以生成上面的屬性了, 不過還需手動改值. 
類似的快捷鍵有: 輸入cw 然后點擊兩下Tab鍵 就可以直接生成Console.WriteLine();了. 類似的還有很多, 在這里就不列舉了.

之所以可以這樣定義屬性, 主要是因為編譯器在編譯時會為我們創建一個私有字段. 利用反編譯工具可以知道使用自動實現的屬性時,C#都會幫我們創建必要的字段.
另外在結構體中使用自動屬性時, 需要注意的是所有構造函數都需要顯式地調用無參構造函數this, 否則會出現編譯錯誤. 因為只有這樣,編譯器才能知道所有的字段都已經被賦值.
然而, 類卻不需要顯式地調用無參構造函數, 這主要是由C#編譯器的設計決定, 我們記住就好了.

 1 public struct TestPerson
 2 {
 3     public string Name { get; set; }
 4     public int Age { get; private set; }
 5 
 6     //結構體中, 不顯式地調用無參構造函數this()時, 會出現編譯時錯誤
 7     public TestPerson(string name)
 8     : this()
 9     {
10         this.Name = name;
11     }
12 }
View Code

2,隱式類型 var關鍵字
C#是強類型語言, 在定義一個變量時, 需要聲明變量的類型. 然而類型的長度過長,就可能影響代碼的可讀寫性. 為了避免這樣的問題, C#3.0 引入了隱式類型,即可以使用關鍵字var來聲明變量或數組.
var關鍵字告訴編譯器去根據變量的值來推斷其類型.

1 class Program
2 {
3     static void Main(stirng[] args)
4     {
5         //用var聲明局部變量
6         var stringValue = "Barry Wang";
7         stringValue = 2;
8     }
9 }
View Code

這里就會報錯"無法將類型int 隱式轉換為string". 調試會發現stringValue是string類型的.
使用隱式類型有一些限制, 包括以下幾點:
(1)被聲明的變量是一個局部變量, 不能為字段
(2)變量在聲明時必須被初始化, 因為編譯器要根據變量的賦值來推斷類型
(3)變量不能初始化為一個方法組, 也不能為一個匿名函數

3,對象集合初始化
在C#3.0之前定義類, 我們往往需要定義多個構造函數來完成不同情況下的初始化, C#3.0 提供了對象初始化器, 它減少了我們在類中定義的構造函數代碼, 從而使代碼更加簡潔.

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     { 
 5         //沒有對象初始化器時的代碼
 6         Person p = new Person("BarryWang", 25);
 7         p.Weight = 60;
 8         p.Height = 170;
 9     }
10 }
11 
12 public class Person
13 {
14     public string Name { get; set; }
15     public int Age { get; set; }
16     public int Weight { get; set; }
17     public int Height { get; set; }
18 
19     //定義不同情況下的初始化函數
20     public Person() { }
21     public Person(string name) { } 
22     public Person(string name, int age) { }
23     public Person(string name, int age, int weight) { }
24     public Person(string name, int age, int weight, int height) 
25     {
26         this.Name = name;
27         this.Age = age;
28         this.Weight = weight;
29         this.Height = height;
30     }
31 }
View Code

在C#3.0引入對象初始化之后, 我們就不需要定義多個構造函數了, 前面的代碼可以簡化為如下的形式:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         //使用對象初始化器初始化
 6         Person p = new Person() {Name = "Barry Wang", Age = 25, Weght = 60, Height = 70};
 7         //下面這行代碼和上面一行是等價的, 只不過下面省略了構造函數的圓括號而已
 8         Person p2 = new Person {Name = "Barry Wang", Age = 25, Weght = 60, Height = 70};
 9     }
10 }
11 
12 public class Person
13 {
14     public string Name { get; set; }
15     public int Age { get; set; }
16     public int Weight { get; set; }
17     public int Height { get; set; }
18 }
View Code

利用反編譯工具可以知道編譯器為對象做了如下處理: 首先C#編譯器生成了一個Person類的臨時對象, 並調用Person類的默認無參構造函數對其初始化.然后對它的屬性逐個賦值.
由此可以想到,要使用對象初始化器,則必須保證類中具有一個無參構造函數. 如果我們自定義了一個有參構造函數而把默認的無參構造函數覆蓋了, 則需要重新定義一個無參構造函數.

再例如 給List 中添加元素, 在C#3.0 之前我們需要一個個Add 添加, 而現在直接可以利用集合初始化器即可, 編譯器會調用Add方法, 一個個地將初始化的內容添加進去.

1 class Program
2 {
3     List<string> newNames = new List<string>
4     {
5         "A", "B", "C"
6     };
7 }
View Code

4,匿名類型
匿名類型顧名思義就是沒有知名類型的類型, 通過隱式類型和對象初始化器兩種特性創建了一個類型未知的對象, 使我們在不定義類型的情況下可以實現對象的創建,從而減少了類定義過程的代碼.

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     { 
 5         //定義匿名類型對象
 6         var person = new {Name = "Barry Wang", Age = 25 };
 7         Console.WriteLine("{0} 的年齡為: {1}", person.Name, person.Age);
 8 
 9         //定義匿名類型數組
10         var personCollection = new[]
11         {
12             new {Name = "Barry", Age = 30},
13             new {Name = "Brad", Age = 22},
14             new {Name = "Tony", Age = 25}
15         };
16 
17         int totalAge = 0;
18         foreach (var p in personCollection)
19         {
20             //下面一行代碼證明了Age屬性時int類型
21             totalAge += p.Age;
22         }
23 
24         Console.WriteLine("所有人的年齡總和為: {0}", totalAge);
25         Console.ReadKey();
26     }
27 }
View Code

5,擴展方法
擴展方法, 首先是一個方法, 他可以用來擴展已定義類型中的方法成員.
如下例所示:

 1 public static class Extensions
 2 {
 3     //對string 類擴展,注意this關鍵字
 4     public static void TestStr(this string s)
 5     {
 6         Console.WriteLine(s.ToString());
 7     }
 8     //對int 類擴展
 9     public static void TestInt(this int i, string s)
10     {
11         Console.WriteLine(s + i.ToString());
12     }
13 }
14 class Program
15 {
16     static void Main(string[] args)
17     {
18         //使用擴展方法
19         string s = "Test Extensions Methods";
20         s.TestStr();//調用方法1
21         Extensions.TestStr(s);//調用方法2
22         int i = 100;
23         i.TestInt("This value is ");
24         Extensions.TestInt(i, "This value is ");
25         Console.ReadKey();
26     }
27 }
View Code

並不是所有的方法都可以用作擴展方法, 我們需要考察它是否符合下列擴展方法的定義規則:
(1)擴展方法必須在一個非嵌套, 非泛型的靜態類中定義
(2)它至少要有一個參數
(3)第一個參數必須加上this關鍵字作為前綴(第一個參數類型也稱為擴展類型, 即指方法對這個類型進行擴展)
(4)第一個參數不能使用任何其他修飾符(如不能使用ref out等)

 1 namespace CurrentNameSpace
 2 {
 3     //要想使用不用命名空間下的擴展方法, 需要先引入該命名空間
 4     using CustomNameSpace;
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Person p = new Person {Name = "BarryWang"};
10             p.Print();//等價於==>ExtensionClass.Print(p);
11             p.Print("Hello");
12             Console.ReadKey();
13         }
14     }
15 
16     //自定義類型
17     public class Person
18     {
19         public string Name { get; set; }
20     }
21 
22     //當前命名空間下的擴展方法定義
23     public static class ExtensionClass
24     {
25         public static void Print(this Person p)
26         {
27             Console.WriteLine("調用的是當前命名空間下的擴展方法輸出, 姓名為: {0}", p.Name);
28         }
29     }
30 }
31 
32 namespace CustomNameSpace
33 {
34     using CurrentNameSpace;
35     public static class CustomExtensionClass
36     {
37         //擴展方法定義
38         public static void Pint(this Person p)
39         {
40             Console.WriteLine("調用的是CustomNameSpace命名空間下擴展方法輸出: 姓名為: {0}", p.Name);
41         }
42 
43         //擴展方法定義
44         public static void Pint(this Person p, string s)
45         {
46             Console.WriteLine("調用的是CustomNameSpace命名空間下擴展方法輸出: 姓名為: {0},附加字符串為{1}", p.Name, s);
47         }
48     }
49 }
View Code

打印結果是:
調用的是當前命名空間下的擴展方法輸出, 姓名為: Barry Wang
調用的是CustomNameSpace命名空間下擴展方法輸出, 姓名為: Barry Wang, 附加字符串為Hello

注解: 大家看到p.Print(); 是否有些疑惑呢? 對於C#3.0編譯器而言, 當它看到某個類型的變量在調用方法時, 它會首先去該對象的實例方法進行查找,如果沒有找到與調用方法同名並參數一致的實例方法,
編譯器就回去查找是否存在合適的擴展方法. 編譯器會檢查所有導入的命名空間和當前命名空間中的擴展方法, 並將變量類型匹配到擴展類型.
從編譯器發現擴展方法的過程來看, 方法調用的優先級別順序為: 類型的實例方法-->當前命名空間下的擴展方法-->導入命名空間的擴展方法.

解釋上面代碼打印結果的由來: 在以上代碼中存在另個不同的命名空間, 它們都定義了帶一個參數的擴展方法Print. 根據執行順序, 編譯器會首先查看Person類型中是否定義了無參的Print實例方法.
如果有, 則停止查找; 否則繼續查找當前命名空間下, 即CurrentNameSpace下是否定義了一個帶參數的擴展方法Print.
此時在CurrentNameSpace命名空間下, 正好存在帶一個擴展類型參數的Print擴展方法, 編譯器因此不會再繼續查找其他引入命名空間了. 所以p.Print() 調用的是當前命名空間下的Print擴展方法.
而p.Print("Hello")的調用也是一個道理.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM