1. 程式人生 > >《Visual C# 2012 從入門到精通》學習筆記

《Visual C# 2012 從入門到精通》學習筆記

第1部分 Visual C#和Visual Studio2012概述

第1章     歡迎進入C#程式設計世界

1.1        開始在Visual Studio 2012環境中程式設計

1.2        編寫第一個程式

1.3        使用名稱空間

作為好習慣,所有類都應該在名稱空間中定義。例如,Console類在System名稱空間中,它的全名為System.Console

第2章     使用變數、操作符和表示式

2.1  理解語句

2.2  使用識別符號

2.3  使用變數

建議變數名稱以小寫字母開頭

2.4  使用基元資料型別

C#不允許使用未賦值的變數

2.5  使用算數操作符

2.6  變數遞增和遞減

如果孤立地使用遞增或遞減操作符,請統一使用字尾形式

2.7  宣告隱士型別的區域性變數

第3章     方法和作用域

3.1  建立方法

3.1.1       宣告方法

宣告C#方法的語法如下:

returnTypemethodName(parameterList) {

      // 方法主體

}

3.1.2       從方法返回資料

3.1.3       呼叫方法

呼叫C#方法的語法如下:

result =method(argumentList);

3.2  使用作用域

3.2.1       定義區域性作用域

3.2.2       定義類作用域

類定義的變數稱為欄位

3.2.3       過載方法

過載方法有相同的方法名,但有不同的引數數量或者/以及不同的引數型別

3.3  編寫方法

3.4  使用可選引數和具名引數

3.4.1       定義可選引數

以下optMethod方法的第一個引數是必需的,因為它沒有提供預設值,但第二個和第三個引數可選:

voidoptMethod(int first, double second = 0.0, string third = “Hello”) {

      …

}

3.4.2       傳遞具名引數

要將實參作為具名引數傳遞,必須輸入一個引數名,一個冒號,然後是要傳遞的值

optMethod(first:99, second: 123.45, third: “World”);

3.4.3       消除可選引數和具名引數的歧義

當過載方法的兩個版本都不能完全匹配提供的實參時,編譯報錯

第4章     使用判斷語句

4.1  宣告布林變數

4.2  使用布林操作符

4.3  使用if語句做出判斷

第5章     使用複合賦值和迴圈語句

5.1  使用複合賦值操作符

5.2  使用while語句

5.3  編寫for語句

5.4  編寫do語句

第6章     管理錯誤和異常

6.1  處理錯誤

6.2  嘗試執行程式碼和捕捉異常

try

{

      int leftHandSide =int.Parse(lhsOperand.Text);

      int rightHandSide =int.Parse(rhsOperand.Text);

      int answer = doCalculation(leftHandSide,rightHandSide);

      result.Text  = answer.ToString();

}

catch(FormatExceptionfEx)

{

      // 處理異常

}

6.2.1       未處理的異常

如果異常未處理,則呼叫方法退出,返回上一級呼叫方法,以此類推,如果遍歷了所有呼叫方法還是找不到匹配的catch處理程式,整個程式終止。

6.2.2       使用多個catch處理程式

6.2.3       捕捉多個異常

6.2.4       傳播異常

6.3  使用checked和unchecked整數運算

6.3.1       編寫checked語句

checked語句中的任何整數運算溢位都會引發OverflowException異常

int number =int.MaxValue;

checked

{

      int willThrow = number++;

      Console.WriteLine(“永遠都執行不到這裡”);

}

unchecked塊中的所有整數運算都不檢查溢位,永遠不引發OverflowException異常

int number =int.MaxValue;

unchecked

{

      int wontThrow = number++;

      Console.WriteLine(“會執行到這裡”);

}

6.3.2       編寫checked表示式

int wontThrow =unchecked(int.MaxValue + 1);

int willThrow =checked(int.MaxValue + 1);

6.4  引發異常

throw語句用於引發異常物件,物件包含異常的細節

6.5  使用finally塊

第2部分  理解C#物件模型

第7章     建立並管理類和物件

7.1  理解分類

7.2  封裝的目的

7.3  定義並使用類

7.4  控制可訪問性

private:只允許從類的內部訪問,但私有僅相對於類,而非相對於物件,同一個類的兩個例項能相互訪問對方的私有物件,但訪問不了其他類的例項中的私有資料

public:既能從類的內部訪問,也能從外部訪問

7.4.1       使用構造器

7.4.2       過載構造器

C#允許將類的原始碼拆分到單獨的檔案中,但要在每個檔案中使用partial關鍵字定義類的不同部分

partial classCircle

{

      // 構造器

}

pratial classCircle

{

      // 欄位和方法

}

7.5  理解靜態方法和資料

如果把方法或欄位宣告為static,就可以使用類名呼叫方法或訪問欄位

7.5.1       建立共享欄位

靜態欄位能在類的所有物件之間共享

7.5.2       使用const關鍵字建立靜態欄位

用const關鍵字宣告的欄位稱為常量欄位,是一種特殊的靜態欄位,它的值永遠不會改變

7.5.3       靜態類

C#允許宣告靜態類,靜態類只能包含靜態成員

7.5.4       匿名類

匿名類是沒有名字的類,建立匿名類的辦法是以new關鍵字開頭,後跟一對{},在大括號中定義想在類中包含的欄位和類

varmyAnonymousObject = new {Name = “John”, Age = 47};

第8章     理解值和引用

8.1  複製值型別的變數和類

C#的大多數基元型別(包括int, float, double和char等,但不包括string)統稱為值型別

類是引用型別的一個例子,引用型別容納了對記憶體塊的引用,C#的string關鍵字僅僅是System.String類的別名

8.2  理解null值和可空型別

變數應該儘量在宣告時初始化

8.2.1       使用可空型別

利用C#定義的?修飾符可將變數宣告為可空值型別

int? i = null;

可以將值型別變數賦給可空變數,但不能將一個可空變數賦給一個普通的值型別變數

8.2.2       理解可空型別的屬性

可空型別公開了兩個屬性,HasValue屬性指出可空型別是包含真正的值,還是包含null值,如果包含真正的值,可以利用Value屬性來獲取這個值

8.3  使用ref和out引數

8.3.1       建立ref引數

為引數(形參)附加ref字首,C#編譯器將生成程式碼傳遞對實參的引用,而不是傳遞實參的拷貝。作為ref引數傳遞的實參也必須附加ref字首。

8.3.2       建立out引數

為引數(形參)附加out字首,使引數成為實參的別名,可向其傳遞未初始化的實參,且在方法內部對其進行賦值

8.4  計算機記憶體的組織方式

作業系統和執行時通常將記憶體劃分為堆和棧兩個區域,所有值型別都在棧上建立,所有引用型別(物件)都在堆上建立,可空型別實際是引用型別,在堆上建立

8.5  System.Object類

C#提供了object關鍵字來作為System.Object的別名

8.6  裝箱

object型別的變數能引用值型別的例項,引用過程是將資料項從棧自動複製到堆中再引用,這種將資料項從棧中自動複製到堆的行為稱為裝箱

8.7  拆箱

為了訪問已裝箱的值,必須進行強制型別轉換,在object變數前新增一對圓括號,並輸入型別名稱

注意,裝箱和拆箱都會產生較大的開銷,因為它們涉及不少檢查工作,而且需要分配額外的堆記憶體

8.8  資料型別的安全轉換

8.8.1       is操作符

可以用is操作符驗證物件的型別是不是自己希望的

if(o  is WrappedInt) {

      WrappedInt temp  =  (WrappedInt)o;

}

8.8.2       as操作符

“執行時”嘗試將物件轉換成指定型別,若轉換成功,就返回轉換成功的結果,若轉換失敗,就返回null

WrappedInt  temp =  o  as WrappedInt;

if(temp  != null) {

      // 只有轉換成功,這裡的程式碼才會執行

}

第9章     使用列舉和結構建立值型別

9.1  使用列舉

9.1.1       宣告列舉

定義列舉要先寫一個enum關鍵字,後跟一對{},然後在{}內新增一組符號,這些符號標識了該列舉型別可以擁有的合法的值

enum  Season { Spring,  Summer, Fall,  Winter }

9.1.2       使用列舉

宣告好列舉之後,可以像使用其他任何型別那樣使用它們

Season  colorful =  Season.Fall;

9.1.3       選擇列舉文字常量值

可以獲取列舉變數的基礎整數值

Console.WriteLine((int)colorful);  // 輸出’2’

可以將特定整數常量和列舉型別的文字常量手動關聯起來

enum  Season {  Spring  =  1,Sumer, Fall, Winter  }

多個列舉文字常量可以擁有相同的基礎值

enum  Season {  Spring, Summer, Fall,  Autumn = Fall, Winter  }

9.1.4       選擇列舉的基本型別

宣告列舉時,列舉的文字常量將獲得int型別的值,但是,也可以讓列舉型別基於不同的基本整數型別。

enum  Season :  Short  { Spring, Summer, Fall, Winter  }

9.2  使用結構

結構是值型別,在棧上儲存,所以能有效地減少記憶體管理的開銷。結構可以包含自己的欄位、方法和構造器

在C#中,基元數值型別int, long和float分別是System.Int32, System.Int64和System.Single這三個結構型別的別名

9.2.1       宣告結構

宣告結構要以struct關鍵字開頭,後跟型別名稱,最後是大括號中的結構主體

struct  Time

{

      public int hours, minutes, seconds;

}

9.2.2       理解結構和類的區別

l  不能為結構宣告預設構造器,自己寫的構造器必須顯示初始化所有欄位

l  類的例項欄位可以在宣告時初始化,但結構不允許

9.2.3       宣告結構變數

定義了一個結構型別之後,可以像使用其他任何型別那樣使用它們

9.2.4       理解結構的初始化

結構的所有欄位都在棧上建立

9.2.5       複製結構變數

可將結構變數初始化或賦值為另一個結構變數,前提是操作符=右側的結構變數已完全初始化(即所有欄位都已初始化)

第10章   使用陣列

陣列是引用型別,是System.Array類的例項

10.1        宣告和建立陣列

10.1.1    宣告陣列變數

宣告陣列變數要先寫它的元素型別名稱,後跟一對方括號,最後寫變數名

int[]  pins;

10.1.2    建立陣列例項

無論元素是什麼型別,陣列始終都是引用型別

pins = newint[4];

10.1.3    填充和使用陣列

將陣列元素初始化為指定的值

int[] pins = newint[4] {9, 3, 7, 2};

10.1.4    建立隱式型別的陣列

如果在宣告陣列時指定了初始值列表,就可以讓C#編譯器自己推斷陣列元素的型別

var name = new[] {“John”, “Diana”, “James”, “Francesca”};

10.1.5    訪問單獨的陣列元素

int myPin;

myPin = pins[2];

所有陣列元素訪問都要進行邊界檢查,使用小於0或者大於等於陣列長度的整數索引時,編譯器會引發IndexOutOfRangeException異常

10.1.6    遍歷陣列

可以用for語句遍歷所有元素

for(int index =0; index < pins.Length; index++)

{

      int pin = pins[index];

      Console.WriteLine(pin);

}

也可以用foreach語句來遍歷陣列的所有元素

foreach(int pinin pins)

{

      Console.WriteLine(pin);

}

修改陣列元素必須使用for語句,因為foreach語句的迴圈變數是陣列的每個元素的只讀拷貝

10.1.7    陣列作為方法引數和返回值傳遞

將陣列宣告為方法引數

public voidProcessData(int [] data)

{

      foreach(int i in data)

      {

           …

      }

}

方法的返回型別是陣列型別

public int[]ReadData()

{

      … // 方法內部要建立並填充陣列,最後返回陣列

}

10.1.8    複製陣列

l  System.Array類的CopyTo方法

int[] copy = newint[pins.Length];

pins.CopyTo(copy,0);  // 從索引0開始複製

l  System.Array類的靜態方法Copy

Array.Copy(pins,copy, copy.Length);

l  System.Array的例項方法Clone

copy =(int[])pins.Clone();

Clone, CopyTo和Copy建立的都是陣列的淺拷貝(如果被複制的陣列包含引用,這些方法只複製引用,不復制被引用的物件)

10.1.9    使用多維陣列

int [,] items =new int[4, 6];  // 建立二維陣列

10.1.10 建立交錯陣列

交錯陣列的每一列的長度都可以不同

int[][] items =new int[4][];

items[0] = newint[3];

items[1] = newint[10];

第11章   理解引數陣列

11.1        回顧過載

過載是指在同一個作用域中宣告宣告兩個或更多同名方法

用params關鍵字宣告的引數陣列允許只寫一個方法就能接受數量可變的引數

11.2        使用陣列引數

11.2.1    宣告引數陣列

用params關鍵字修飾陣列引數

class Util

{

      public static int Min(params int[] paramList)

      {

           // 程式碼和以前一樣

      }

}

呼叫該方法時,可以傳遞任意數量的整數引數,而不需建立引數陣列

int min =Util.Min(first, second);

11.2.2    使用params object[]

使用params object[]引數陣列,允許方法接受任意數量任意型別的引數

class Black

{

      public static void Hole(params object[]paramList)

      {

           …

      }

}

Black.Hole(“fortytwo”, 42);

Console.WriteLine方法

public staticvoid WriteLine(string format, params object[] arg);

11.2.3    使用引數陣列

11.3        比較引數陣列和可選引數

第12章   使用繼承

12.1        什麼是繼承

在程式設計中,繼承的問題就是分類的問題

12.2        使用繼承

為了宣告一個類從另一個類繼承,需要使用以下語法

classDerivedClass : BaseClass

{

      …

}

12.2.1    複習System.Object類

System.Object類是所有類的跟,所有類都隱式派生自System.Object類

12.2.2    呼叫基類構造器

作為好的程式設計實踐,派生類的構造器在執行初始化時,最好使用base關鍵字呼叫它的基類構造器

class Horse :Mammal

{

      public Horse(string name) : base(name)

      {

           …

      }

}

12.2.3    類的賦值

可以將一種型別的物件賦給繼承層次結構中較高位置的一個類的變數,但反過來則不可以

12.2.4    宣告新方法

派生類中的方法會遮蔽(或隱藏)基類中具有相同簽名(方法簽名由方法名、引數數量和引數型別共同決定)的方法,但編譯器會顯示警告,可以使用new關鍵字消除警告

class Mammal

{

      public void Talk()

      {

           …

      }

}

class Horse :Mammal

{

      new public void Talk()

      {

           …

      }

}

12.2.5    宣告虛方法

故意設計成被重寫的方法稱為虛方法,使用virtual關鍵字來標記虛方法

namespace System

{

      class Object

      {

           public virtual string ToString()

           {

                 …

           }

      }

}

12.2.6    宣告重寫方法

派生類用override關鍵字重寫基類的虛方法,從而提供該方法的另一個實現

class Horse :Mammal

{

      public override string ToString()

      {

           …

      }

}

如果派生類不用override關鍵字宣告方法,就不是重寫基類方法,而是隱藏方法

12.2.7    理解受保護的訪問

允許基類的派生類訪問基類的部分成員,同時要阻止不屬於這個繼承層次結構的類訪問這些成員,在這種情況下,就可以使用protected關鍵字來標記成員

12.3        理解擴充套件方法

擴充套件方法允許新增靜態方法來擴充套件現有的型別(無論類還是結構),擴充套件方法在靜態類中定義,被擴充套件的型別必須是第一個引數,而且必須附加this關鍵字

static classUtil

{

      public static int Negate(this int i)

      {

           return –i;

      }

}

呼叫擴充套件方法Negate()

int x = 591;

Console.WriteLine(“x.Negate{0}”, x.Negate());

第13章   建立介面和定義抽象類

介面不包含任何程式碼或資料,它只規定了從介面繼承的類必須提供哪些方法和屬性

抽象類在許多方面都和介面相似,只是它們可以包含程式碼和資料

13.1        理解介面

介面描述了類提供的功能,但不描述功能如何實現

13.1.1    定義介面

介面不允許指定任何訪問修飾符,介面中的方法是沒有實現的,它們只是一個宣告,方法主體被替換成一個分號,介面不含任何資料,不可以向介面新增任何欄位

interfaceIComparable

{

      int CompareTo(object obj);

}

13.1.2    實現介面

為了實現介面,需要宣告類或結構從介面繼承,並實現介面指定的全部方法

interfaceILandBound

{

      int NumberOfLegs();

}

實現介面ILandBound

class Horse :ILandBound

{

      public int NumberOfLegs()

      {

           return 4;

      }

}

一個類可以在擴充套件另一個類的同時實現介面,C#用位置記號法來區分基類和介面,首先寫基類名,在寫逗號,最後寫介面名。

interfaceILandBound

{

      …

}

class Mammal

{

      …

}

class Horse :Mammal, ILandBound

{

      …

}

13.1.3    通過介面來引用類

介面變數能引用實現了該介面的類的物件,可由此定義方法來獲取不同型別的引數-只要型別實現了指定的介面

intFindLandSpeed(ILandBound landBoundMammal)

{

      …

}

13.1.4    使用多個介面

13.1.5    顯式實現介面

顯式實現介面:在實現時指明方法從屬於哪個介面

class Horse :ILandBound, IJourney

{

      int ILandBound.NumberOfLegs()

      {

           return 4;

      }

      int IJourney.NumberOfLegs()

      {

           return 3;

      }

}

注意,顯式顯現的方法是私有的,需要通過恰當的介面來引用物件

Horse horse =new Horse()

IJourneyjourneyHorse = horse;

intlegsInJourney = journeyHorse.NumberOfLegs();

13.1.6    介面的限制

13.1.7    定義和使用介面

13.2        抽象類

為了明確宣告不允許建立某個類的例項,必須將那個類顯式宣告為抽象類,用abstract關鍵字來完成

abstract classGrazingMammal : Mammal, IGrazable

{

      …

}

抽象類可以包含抽象方法,抽象方法與虛方法相似,只是它不含方法主體。抽象方法在以下情形下使用:一個方法在抽象類中提供預設實現沒有意義,但又需要繼承類提供該方法的實現

abstract classGrazingMammal : Mammal, IGrazable

{

      abstract void DigestGrass();

}

13.3        密封類

如果不想一個類作為基類使用,可以使用C#提供的sealed關鍵字防止類被用作基類

sealed classHorse : GrazingMammal, ILandBound

{

      …

}

13.3.1    密封方法

可用sealed關鍵字密封繼承到的虛方法,阻止當前類的派生類繼續重寫該方法

interface(介面)引入方法的名稱

virtual(虛)是方法的第一個實現,可由派生類重寫

override(重寫)是派生類重寫的實現,是方法的第二個實現

sealed(密封)是方法的最後一個實現,再下面的派生類不能重寫了

13.3.2    實現並使用抽象類

第14章   使用垃圾回收和資源管理

14.1        物件的生存期

14.1.1    編寫析構器

如果託管的資源很大(比如多維陣列),就可考慮將對該資源的所有引用都設為null,如果物件應用了非託管資源(如檔案類、網路連線、資料庫連線),析構器就更有用了

classFileProcessor

{

      FileStream file = null;

      public FileProcessor(string fileName)

      {

           this.file = File.OpenRead(fileName);// 開啟檔案

      }

      ~FileProcessor() // 析構器

      {

           this.file.Close();  // 關閉檔案

      }

}

14.1.2    為什麼要使用垃圾回收器

14.1.3    垃圾回收器的工作原理

14.1.4    慎用析構器

除非確有必要,否則請儘量避免使用析構器

14.2        資源管理

14.2.1    資源清理方法

TextReader類包含虛方法Close,它負責關閉流,這就是一個資源清理方法

TextReaderreader = new StreamReader(filename);

string line;

while((line =reader.ReadLine()) != null)

{

      Console.WriteLine(line);

}

reader.Close();

14.2.2    異常安全的資源清理

為了確保資源清理方法總是得到呼叫,無論是否發生異常,一個辦法是在finally塊中呼叫該方法

TextReaderreader = new StreamReader(filename);

try

{

      string line;

      while((line = reader.ReadLine()) != null)

      {

           Console.WriteLine(line);

      }

}

finally

{

      reader.Close();

}

14.2.3    using語句和IDisposable介面

using語句提供了一個脈絡清晰的機制來控制資源的生存期,可以建立一個物件,這個物件會在using語句塊結束時銷燬

using ( 型別 變數 = 初始化)

{

      語句塊

}

using(TextReader reader = new StreamReader(filename))

{

      string line;

      while((line = reader.ReadLine() != null)

      {

           Console.WriteLine(line);

      }

}

using語句宣告的變數的型別必須實現IDisposable介面

namespace System

{

      interface IDisposable

      {

           void Dispose();

      }

}

14.2.4    從析構器中呼叫Diapose方法

14.3        實現異常安全的資源清理

第3部分  用C#定義可擴充套件型別

可擴充套件型別-即可以在不同應用程式中重用的功能元件

第15章   實現屬性以訪問欄位

15.1        使用方法實現封裝

15.2        什麼是屬性

屬性是欄位和方法的交集-看起來像欄位,用起來像方法

訪問修飾符  型別  屬性名

{

      get

      {

           // 取值程式碼

      }

      set

      {

           // 賦值程式碼

      }

}

structScreenPosition

{

      private int _x, _y;

      public ScreenPosition(int X, int Y)

      {

           this._x = rangeCheckedX(X);

           this._y = rangeCheckedY(Y);

      }

      public int X

      {

           get {return this._x;}

           set {this._x = rangeCheckedX(value);}

      }

      public int Y

      {

           get {return this._y;}

           set {this._y = rangeCheckedY(value);}

      }

      private static int rangeCheckedX(int x) {…}

      private static int rangeCheckedY(int y) {…}

}

15.2.1    使用屬性

ScreenPositionorigin = new ScreenPosition(0, 0);

int xpos =origin.X;  // 實際呼叫origin.X.get

origin.X =40;  // 實際呼叫origin.X.set

15.2.2    只讀屬性

可以宣告只包含get訪問器的屬性,這稱為只讀屬性

15.2.3    只寫屬性

類似地,可宣告只包含set訪問器的屬性,這稱為只寫屬性

15.2.4    屬性的可訪問性

在屬性宣告中,可為get和set訪問器單獨指定可訪問性,從而覆蓋屬性的可訪問性

15.3        理解屬性的侷限性

應該儘可能採取面向物件的設計,將重點放在物件的行為而不是屬性上

15.4        在介面中宣告屬性

interfaceIScreenPosition

{

      int X { get; set; }

      int Y { get; set; }

}

15.5        生成自動屬性

class Circle

{

      public int Radius { get; set; }

}  // C#編譯器自動將這個定義轉換成私有欄位以及一個預設的實現

15.6        使用屬性來初始化物件

將私有變數初始化為一組預設值並將它們作為屬性公開,建立類的例項時,可為提供了set訪問器的任何公共屬性指定名稱和值

第16章   使用索引器

16.1        什麼是索引器

索引器可被視為一種智慧陣列,索引器封裝了一組值,使用索引器時,語法和使用陣列完全相同

16.1.1    不是索引器的例子

16.1.2    使用索引器的同一個例子

假定型型別名為IntBits,IntBits將包含一個int值,但我們要將IntBits作為由bool變數構成的陣列

struct IntBits

{

      private int bits;

      public IntBits(int iniitalBitValue)

      {

           bits = initialBitValue;

      }

      public bool this [ int index ]

      {

           get

           {

                 return (bits & (1 <<index)) != 0;

           }

           set

           {

                 if(value)  // 如果value為true,就將指定的位設為1,否則設為0

                      bits |= (1 <<index);

                 else

                      bits &= ~(1 <<index);

           }

      }

}

使用索引器

int adapted =126;

IntBits bits =new IntBits(adapted);

bool peek =bits[6];  // 獲取索引位置6的boo值

bits[0] =true;  // 將索引0的位設為ture(1)                  

16.1.3    理解索引器的訪問器

16.1.4    對比索引器和陣列

16.2        介面中的索引器

interfaceIRawInt

{

      bool this [ int index ] { get; set; }

}

16.3        在Windows應用程式中使用索引器

第17章   泛型概述

17.1        泛型概述

17.2        泛型解決方案

C#通過泛型避免進行強制型別轉換,增強型別安全性,減少裝箱量,C#是在尖括號中提供型別引數來指定泛型類

classQueue<T>

{

      …

}  // T就是型別引數,它作為佔位符使用,會在編譯時被真正的型別取代

型別引數T會被建立物件時指定的型別取代

Queue<int>intQueue = new Queue<int>();

intQueue.Enqueue(99);

int myInt =intQueue.Dequeue();

17.2.1    對比泛型類與常規類

17.2.2    泛型和約束

約束限制泛型類的型別引數實現了一組特定的介面

public classPrintableCollection<T> where T : IPrintable // 這個類編譯時,編譯器會驗證用於替換T的型別是否實現了IPrintable介面

17.3        建立泛型類

17.3.1    二叉樹理論

二叉樹是一種遞迴(自引用)資料結構,它要麼為空,要麼包含3個元素:一個數據(節點)和兩個子書(也是二叉樹)。

17.3.2    使用泛型構造二叉樹類

17.4        建立泛型方法

17.5        可變性和泛型介面

17.5.1    協變介面

interfaceIRetrieveWrapper<out T>

{

      T GetData();

}

這個功能稱為協變性,只要存在從型別A到型別B的有效轉換,或者型別A派生自型別B,就可以將IretrieveWrapper<A>物件賦給IRetrieveWrapper<B>引用

Wrapper<string>stringWrapper = new Wrapper<string>();

IRetrieveWrapper<object>retrievedObjectWrapper = stringWrapper;

17.5.2    逆變介面

public interfaceIComparer<in T>

{

      int Compare(T x, T y);

}

in關鍵字明確告訴C#編譯器,要麼傳遞T作為方法的引數型別,要麼傳遞從T派生的任何型別,T不能作為任何方法的返回型別使用

逆變性允許使用泛型介面通過A型別的一個引用來引用B型別的一個物件,只要A是從B派生的

第18章   使用集合

18.1        什麼是集合類

18.1.1    List<T>集合類

using System;

usingSystem.Collections.Generic;

List<int>numbers = new List<int>();

foreach(intnumber in new int[12]{10, 9, 8, 7, 7, 6, 5, 10, 4, 3, 2, 1})

{

      numbers.Add(number);  // 使用Add方法填充List<int>

}

numbers.Insert(numbers.Count– 1, 99);  // 在列表倒數第二個位置插入元素99

numbers.Remove(7);  // 刪除是7的第一個元素

numbers.RemoveAt(6);  // 刪除當前第7個元素,索引為6

for(int i = 0; I< numbers.Count; i++)

{

      int number = numbers[i];  // 注意可以使用陣列語法

      Console.WriteLine(number);

}

18.1.2    LinkedList<T>集合類

using System;

usingSystem.Collections.Generic;

foreach(intnumber in new int[] {10, 8, 6, 4, 2})

{

      numbers.AddFirst(number);  // 使用AddFirst方法填充列表

}

// 用for語句遍歷

for(LinkedListNode<int>node = numbers.First; node != null; node = node.Next)

{

      int number = node.Value;

      Console.WriteLine(number);

}

// 用foreach語句遍歷

foreach(intnumber in numbers)

{

      Console.WriteLine(number);

}

// 反向遍歷,只能用for語句,foreach語句只能正向

for(LinkedListNode<int>node = numbers.Last; node != null; node = node.Previous)

{

      int number = node.Value;

      Console.WriteLine(number);

}

18.1.3    Queue<T>集合類

using System;

usingSystem.Collections.Generic;

Queue<int>numbers = new Queue<int>();

// 填充佇列

foreach(intnumber in new int[4] {9, 3, 7, 2})

{

      numbers.Enqueue(number);

}

// 遍歷佇列

foreach(intnumber in numbers){

{

      Console.WriteLine(number);

}

// 清空佇列,先進先出

while(numbers.Count> 0)

{

      int number = numbers.Dequeue();

      Console.WriteLine(“{0} has left the queue”,number);

}

18.1.4    Stack<T>集合類

using System;

usingSystem.Collections.Generic;

Stack<int>numbers = new Stack<int>();

// 填充棧-入棧

foreach(intnumber in new int[4] {9, 3, 7, 2})

{

      numbers.Push(number);

}

// 遍歷棧

foreach(intnumber in numbers)

{

      Console.WriteLine(number);

}

// 清空棧,後入先出

while(numbers.Count> 0)

{

      int number = numbers.Pop();

}

18.1.5    Dictionary<TKey, TValue>集合類

using System;

usingSystem.Collections.Generic;

Dictionary<string,int> ages = new Dictionary<string, int>();

// 填充字典

ages.Add(“John”,47);  // 使用Add方法

ages.Add(“Diana”,46);

ages[“James”] =20;  // 使用陣列方法

ages[“Francesca”]= 18;

// 遍歷字典

foreach(KeyValuePair<string,int> element in ages)

{

      string name = element.Key;

      int age = element.Value;

      Console.WriteLine(“Name: {0}, Age: {1}”,name, age);

}

18.1.6    SortedList<TKey, TValue>集合類

SortedList<TKey,TValue>類與Dictionary<TKey,TValue>類非常相似,只是前者的keys陣列總是排好序的

using System;

usingSystem.Collections.Generic;

SortedList<string,int> ages = new SortedList<string, int>();

//填充有序列表

ages.Add(“John”,47);

ages.Add(“Diana”,46);

ages[“James”] =20;

ages[“Francesca”]= 18;

// 遍歷有序列表

foreach(KeyValuePair<string,int> element in ages)

{

      string name = element.Key;

      int age = element.Value;

      Console.WriteLine(“Name: {0}, Age: {1}”,name, age);

}

18.1.7    HashSet<T>集合類

using System;

usingSystem.Collections.Generic;

HashSet<string>employees = new HashSet<string>(new string[]{“Fred”, “Bert”, “Harry”, “John”});

HashSet<string>customers = new HashSet<string>(new string[]{“John”, “Sid”, “Harry”, “Diana”});

employees.Add(“James”);

customers.Add(“Francesca”);

Console.WriteLine(“Employees:”);

foreach(stringname in employees)

{

      Console.WriteLine(name);

}

Console.WriteLine(“\nCustomers:”);

foreach(stringname in customers)

{

      Console.WriteLine(name);

}

Console.WriteLine(“\nCustomerswho are also employees:”);  // 既是客戶又是員工的人

customers.IntersectWith(employees);  // 是破壞性的方法,customers內容會被覆蓋

foreach(stringname in customers)

{

      Console.WriteLine(name);

}

18.2        使用集合初始化器

List<int>numbers = new List<int>(){10, 9, 8, 7, 6, 5, 4, 3, 2, 1};  // 必須支援Add方法

Dictionary<string,int> ages = new Dictionary<string, int>(){{“John”, 47}, {“Diana”, 48},{“James”, 21}, {“Francesca”, 18}};  // 對於獲取鍵/值對的集合

18.3        Find方法、謂詞和Lambda表示式

對於List<T>和LinkedList<T>等支援無鍵隨機訪問的集合,它們無法通過陣列語法來查詢項,所以專門提供了Find方法。

Find方法的實參是代表搜尋條件的謂詞。謂詞就是一個方法,它檢查集合的每一項,返回Boolean值指出該項是否匹配

謂詞最好用Lambda表示式,Lambda表示式只包含引數列表和方法主體

struct Person

{

      public int ID { get; set; }

      public string Name { get; set; }

      public int Age { get; set; }

}

// 建立並填充personnel列表

List<Person>personnel = new List<Person>()

{

      new Person() { ID = 1, Name = “John”, Age= 47 },

      new Person() { ID = 2, Name = “Sid”, Age =28 },

      new Person() { ID = 3, Name = “Fred”, Age= 34 },

      new Person() { ID = 4, Name = “Paul”, Age= 22 },

};

// 查詢ID為3的第一個列表成員

Person match =personnel.Find((Person p) => { return p.ID == 3; });

Console.WriteLine(“ID:{0} \n Name: {1} \n Age: {2}”, match.ID, match.Name, match.Age);

如果Lambda表示式的主體只包含一個表示式,大括號和分號就可省略;如果表示式只有一個引數,圓括號就可省略;另外,許多時候引數型別都可省略。因而,上面的Lambda表示式的簡化版為:

Person match =personnel.Find(p => p.ID == 3);

18.4        比較陣列和集合

第19章   列舉集合

19.1        列舉集合中的元素

只能使用foreach遍歷可列舉集合,可列舉集合就是實現了System.Collections.IEnumerable介面的集合,IEnumerable介面包含一個名為GetEnumerator的方法,GetEnumerator方法返回實現了System.Collections.IEnumerator介面的列舉器物件

IEnumerator介面指定了以下屬性和方法:

object Current {get; }

bool MoveNext();

void Reset();

19.1.1    手動實現列舉器

19.1.2    實現IEnumerable介面

19.2        使用迭代器來實現列舉器

迭代器是能生成已排序值序列的一個程式碼塊

19.2.1    一個簡單的迭代器

可以這樣來想象yield語句:它臨時將方法“叫停”,將一個值傳回呼叫者

19.2.2    使用迭代器為Tree<Titem>類定義列舉器

第20章   分離應用程式邏輯並處理事件

事件通知發生了緊急事件,委託規定在發生事件時應該執行某個方法

20.1        理解委託

委託是對方法的引用

Processor p =new Processor();

delegate …performCalculationDelegate…; // 宣告委託,具體語法省略

performCalculationDelegate= p.performCalculation; // 將方法賦給委託

perfomCalculationDelegate();  // 通過委託呼叫方法

20.1.1    .NET Framework類庫委託例子

List<T>類的Find和Exists方法中謂詞其實就是委託,只不過它恰好返回Boolean值而已

20.1.2    自動化工廠的例子

20.1.3    不使用委託來實現工廠

20.1.4    使用委託來實現工廠

class Controller

{

      delegate voidstopMachineryDelegate();  // 宣告委託型別

      private stopMachineryDelegatestopMachinery;  // 建立委託例項

      public Controller()

      {

           this.stopMachinery +=folder.StopFolding;  // 使委託引用方法

      }

      public void Add(stopMachineryDelegatestopMethod)

      {

           this.stopMachinery +=stopMethod;  // 把方法新增到委託中

      }

      public void Remove(stopMachineryDelegatestopMethod)

      {

           this.stopMachinery -=stopMethod;  // 從委託中移除方法

      }

20.1.5    宣告和使用委託

20.2        Lambda表示式

20.2.1    建立方法介面卡

介面卡是指一個特殊方法,它能轉化一個方法,為它提供不同的簽名

voidFinishFolding()

{

      folder.StopFolding(0);  // 呼叫StopFolding方法

}

this.stopMachinery+= folder.FinishFolding;

}

可以使用以下Lambda表示式:

this.stopMachinery+= (() => folder.StopFolding(0));

20.2.2    Lambda表示式的形式

20.3        啟用事件通知

20.3.1    宣告事件

事件的型別必須是委託,而且必須在宣告前附加event字首

classTemperatureMonitor

{

      public delegate voidStopMachienryDelegate();  // 宣告委託

      public event StopMachienryDelegate MachineOverheating;  // 宣告事件

}

20.3.2    訂閱事件

把方法新增到事件中-這個過程稱為訂閱事件或者向事件登記

TemperatureMonitortempMonitor = new TemperatureMonitor();

tempMonitor.MachineOverheating+= () => {folder.StopFolding(0);};  //使用Lambda表示式

tempMonitor.MachineOverheating+= welder.FinishWelding;

20.3.3    取消訂閱事件

-=操作符用於取消訂閱事件或者從事件登出

20.3.4    引發事件

可以把事件當做方法來呼叫,從而引發該事件

private voidNotify()

{

      if(this.MachineOverheating != null)

      {

           this.MachineOverheating();  // 引發事件

      }

}

20.4        理解使用者介面事件

第21章   使用查詢表示式來查詢記憶體中的資料

21.1        什麼是語言整合查詢

語言整合查詢(Language Integrated Query, LINQ)是對查詢機制進行的抽象

21.2        在C#應用程式中使用LINQ

LINQ要求資料用實現了IEnumerable或IEnumerable<T>介面的資料結構進行儲存,即是可列舉的

21.2.1    選擇資料

IEnumerable<string>customerFirstNames = customers.Select(cust => cust.FirstName);  // 選擇customers陣列中每個客戶的FirstName組成的列表

foreach(stringname in customerFirstNames)

{

      Console.WriteLine(name);  // 列印資料

}

21.2.2    篩選資料

IEnumerable<string>usCompanies =

addresses.Where(addr => String.Equals(addr.Country, “UnitedStates”))

      .Select(usComp =>usComp.CompanyName);

首先應用Where方法,從而篩選出行;再應用Select方法,從而指定其中特定的欄位

foreach(stringname in usCompanies)

{

      Console.WriteLine(name);

}

21.2.3    排序、分組和聚合資料

IEnumerable<string>companyNames =

      addresses.OrderBy(addr =>addr.CompanyName)

.Select(comp => comp.CompanyName);

以升序顯示addresses陣列中的每家公司的名字

varcompaniesGroupedByCountry = addresses.GroupBy(addrs => addrs.Country);

foreach(varcompaniesPerCountry in companiesGroupedByCountry)

{

      Console.WriteLine(“Country: {0} \t {1}companies”,

           companiesPerCountry.Key,companiesPerCountry.Count());

      foreach(var companies incompaniesPerCountry)

      {

           Console.WriteLine(“\t{0}”,companies.CompanyName);

}

按照國家對addresses陣列中的公司進行分組

intnumberOfCompanies = addresses.Select(addr => addr.CompanyName).Count(0;

Console.WriteLine(“Numberof companies: {0}”, numberOfCompanies);

統計addresses陣列中公司的個數

21.2.4    聯接資料

varcompaniesAndCustomers = customers

      .Select(c => new { c.FirstName,c.LastName, c.CompanyName })

      .Join(addresses, custs =>custs.CompanyName,

addrs => addrs.CompanyName,

(custs, addrs) => new { custs.FirstName, custs.LastName,addrs.Country });

以CompanyName為匹配鍵,聯接customers陣列和addresses陣列

21.2.5    使用查詢操作符

varcustomerFirstNames = from cust in customers

                        select cust.FirstName;  // 獲取每個客戶的FirstName

使用from和select操作符

varusCompanyNames = from a in addresses

                     where String.Equals(a.Country, “UnitedStates”)

                     select a.CompanyName;  // 使用where操作符

var companyNames= from a in addresses

                 orderby a.CompanyName

                 select a.CompanyName;  // 使用orderby操作符

還有group操作符、join操作符

21.2.6    查詢Tree<TItem>物件中的資料

21.2.7    LINQ和推遲求值

LINQ擴充套件方法執行時,應用程式不會真正構建集合,只有在遍歷集合時,才會對集合進行列舉

但LINQ提供了ToList方法來構建靜態List物件以包含資料的快取拷貝

第22章   操作符過載

22.1        理解操作符

22.1.1    操作符的限制

22.1.2    過載操作符

struct Hour

{

      private int value;

      public Hour(int initialValue)

      {

           this.value = initialValue;

      }

      public static Hour operator +(Hour lhs,rhs)

      {

           return new Hour(lhs.value +rhs.value);

      }

}

過載操作符同方法一樣有返回型別和引數,只是方法名為關鍵字operator和操作符

22.1.3    建立對稱操作符

public staticHour operator +(Hour lhs, int rhs)

{

      return lhs + new Hour(rhs);

}

public staticHour opertor +(int lhs, Hour rhs)

{

      return new Hour(lhs) + rhs;

}

22.2        理解複合賦值

22.3        宣告遞增和遞減操作符

22.4        比較結構和類中的操作符

22.5        定義成對的操作符

22.6        實現操作符

22.7        理解轉換操作符

22.7.1    提供內建轉換

隱式轉換也稱為擴大轉換,因為結果比原始值的範圍大

顯式轉換也稱為收縮轉換,結果比原始值的範圍小

22.7.2    實現使用者自定義的轉換操作符

struct Hour

{

      public static implicit operator int (Hourfrom)

      {

           return this.value;

      }

      private int value;

}

implicit為隱式轉換關鍵字,將Hour隱式轉換為int。explicit為顯式轉換關鍵字

22.7.3    再論建立對稱操作符

public staticHour operator +(Hour lhs, Hour rhs)

{

      return new Hour(lhs.value + rhs.value);

}

pblic staticimplicit operator Hour (int from)

{

      return new Hour(from);

}

void Example(Hour a, int b)

{

      Hour eg1 = a + b;  // b隱式轉換成Hour

      Hour eg1 = b + a;  // b 隱式轉換成Hour

}

22.7.4    新增隱式轉換操作符

第4部分  使用C#構建Windows8 專業應用

第23章   使用任務提高吞吐量

第24章   通過非同步操作提高響應速度

第25章   實現Windows Store應用程式的使用者介面

第26章   在Windows Store應用程式中顯示和搜尋資料

第27章   在Windows Store應用程式中訪問遠端資料庫