默認情況下,類聲明為內部的,即只有當前項目中的代碼才能訪問它。可以使用internal訪問修飾符關鍵字顯示指定。
除了兩個訪問修飾符關鍵字(public, internal)外,還可以指定類是抽象的(不能實例化,只能繼承,可以有抽象成員)或密封的(sealed,不能繼承)。為此,可以使用兩個互斥的關鍵字abstract或sealed。
編譯器不允許派生類的可訪問性高於基類。
不能再接口中使用關鍵字abstract和sealed,因為這兩個修飾符在接口定義中是沒有意義的(它們不包含實現代碼,所以不能直接實例化,且必須是可以繼承的)。
在.NET中使用的析構函數(由System.Object類提供)叫做Finalize(),但這不是我們用於聲明析構函數的名稱。使用下面的代碼,而不是重寫Finalize():
| class MyClass { ~MyClass() { // Destructor body. } } |
類的析構函數由帶有~前綴的類名(與構造函數相同)來聲明。
無論在派生類上使用什么構造函數(默認的構造函數或非默認的構造函數),除非明確指定,否則就使用基類的默認構造函數。
base關鍵字指定.NET實例化過程使用基類中有指定參數的構造函數。
除了base關鍵字外,這里還可以將另一個關鍵字this用作構造函數初始化器。這個關鍵字指定在調用指定的構造函數前,.NET實例化過程對當前類使用非默認的構造函數。例如:
| public class MyDerivedClass : MyBaseClass { public MyDerivedClass() : this(5,6) { } ... public MyDerivedClass (int i, int j) : base (i) { } } |
這段代碼將執行下述序列:
執行System.Object.Object()構造函數。
執行MyBaseClass.MyBaseClass(int i)構造函數。
執行MyDerivedClass.MyDerivedClass(int i, int j)構造函數。
執行MyDerivedClass.MyDerivedClass()構造函數。
包含關系:一個類包含另一個類。這類似於繼承關系,但包含類可以控制對被包含類的成員的訪問,甚至在使用被包含類的成員前進行其他處理。
集合關系:一個類用作另一個類的多個實例的容器。這類似於對象數組,但集合有其他功能,包括索引、排序和重新設置大小等。
值類型和引用類型的一個主要區別是:值類型總是包含一個值,而引用類型可以是null,表示它們不包含值。但是,可以使用可空類型(這時泛型的一種形式)創建一個值類型,使值類型在這個方面的行為方式類似於引用類型(即可以為null)。
只有string和object簡單類型是引用類型,但數組也是隱式的引用類型。
如果一個項目什么都不包含,只包含類(以及其他相關的類型定義,但沒有入口點),該項目就稱為類庫。
類庫項目編譯為.dll程序集,在其他項目中添加對類庫項目的引用,就可以訪問它的內容,這將擴展對象提供的封裝性。
應用程序使用外部庫中定義的類時,可以把該應用程序稱為庫的客戶應用程序。使用所定義的類的代碼一般簡稱為客戶代碼。
接口和抽象類
相同點:都包含可以由派生類繼承的成員。都不能直接實例化,但可以聲明這些類型的變量。
不同點:派生類只能繼承一個基類,即只能直接繼承一個抽象類。而類可以使用任意多個接口。抽象類可以擁有抽象成員(沒有代碼體,且必須在派生類中實現,否則派生類本身必須也是抽象的)和非抽象成員(它們擁有代碼體,也可以是虛擬的,這樣就可以在派生類中重寫)。另一方面,接口成員必須都在使用接口的類上實現——它們沒有代碼體。另外,按照定義,接口成員是公共的(因為它們傾向於在外部使用),但抽象類的成員可以是私有的(只要它們不是抽象的)、受保護的、內部的或受保護的內部成員(其中受保護的內部成員只能在應用程序的代碼或派生類中訪問)。此外,接口不能包含字段、構造函數、析構函數、靜態成員或常量。
抽象類主要用作對象系列的基類,共享某些主要特性,例如,共同的目的和結構。接口則主要用於類,這些類在基礎水平上有所不同,但仍可以完成某些相同的任務。
淺度和深度復制
淺度復制(shallow copy),沒有考慮引用類型成員。因此,新對象中的引用成員就會指向與源對象中相同成員的對象。可以通過派生於System.Object的MemberwiseClone()方法來完成。
深度復制(deep copy),創建成員的新實例(復制值,而不復制引用)。可以實現ICloneable接口,實現其中包含的Clone()方法。這個方法返回一個類型為System.Object的值。
成員的訪問級別:
- public——成員可以由任何代碼訪問。
- private——成員只能由類中的代碼訪問(如果沒有使用任何關鍵字,就默認使用這個關鍵字)。
- internal——成員只能由定義它的程序集(項目)內部的代碼訪問。
- protected——成員只能由類或派生類中的代碼訪問。
后兩個關鍵字可以合並使用,所以也有protected internal成員。它們只能由項目(更確切地講,是程序集)中派生類的代碼來訪問。
定義字段
字段也可以使用關鍵字readonly,表示這個字段只能在執行構造函數的過程中賦值,或由初始化賦值語句賦值。例如:
| class MyClass { Public readonly int MyInt=17; } |
定義方法
可以在方法定義中使用下述關鍵字:
- virtual——方法可以重寫。
- abstract——方法必須在非抽象的派生類中重寫(只用於抽象類中)。
- override——方法重寫了一個基類方法(如果方法被重寫,就必須使用該關鍵字)。
- extern——方法定義在其他地方。
1 public class MyBaseClass 2 { 3 public virtual void DoSomething() 4 { 5 // Base implementation. 6 } 7 } 8 9 public class MyDerivedClass : MyBaseClass 10 { 11 public override sealed void DoSomething() 12 { 13 // Derived class implementation, override base implementation. 14 } 15 } 16 17 }
如果使用了override, 也可以使用sealed指定在派生類中不能對這個方法作進一步的修改,即這個方法不能由派生類重寫。
定義屬性
屬性擁有兩個類似於函數的塊,一個塊用於獲取屬性的值,另一個塊用於設置屬性的值。這兩個塊也稱為訪問器,分別用get和set關鍵字來定義,可以用於控制對屬性的訪問級別。例如:
| private int myInt;
public int MyIntProp { get { return myInt; } protected set { myInt=value; } } |
屬性可以使用virtual、override和abstract關鍵字,就像方法一樣,但這幾個關鍵字不能用於字段。訪問其可以有自己的可訪問性。
訪問器可以使用的訪問修飾符取決於屬性的可訪問性,訪問器的可訪問性不能高於它所屬的屬性。也就是說,私有屬性對它的訪問器不能包含任何可訪問修飾符,而公共屬性可以對其訪問器使用所有的可訪問修飾符。
“重構”表示使用工具修改代碼,而不是手工修改。
如:為字段創建屬性:右擊該字段,選擇Refactor|Encapsulate Field。
| 自動屬性 |
public int MyIntProp { get; set; } |
自動屬性的唯一限制是它們必須包含get和set存取器,無法使用這種方式定義只讀或只寫屬性。
隱藏基類方法
當從基類繼承一個(非抽象的)成員時,也就繼承了其實現代碼。如果繼承的成員是虛擬的,就可以用override關鍵字重寫這段實現代碼。無論繼承的成員是否為虛擬,都可以隱藏這些實現代碼。
使用new關鍵字可以顯示隱藏基類方法。
1 namespace test 2 { 3 public class MyBaseClass 4 { 5 public virtual void DoSomething() 6 { 7 Console.WriteLine("Base imp"); 8 } 9 } 10 11 public class MyDerivedClass : MyBaseClass 12 { 13 new public void DoSomething() 14 { 15 Console.WriteLine("derived imp"); 16 } 17 } 18 19 class Program 20 { 21 static void Main(string[] args) 22 { 23 MyDerivedClass myObj = new MyDerivedClass(); 24 MyBaseClass myBaseObj; 25 myBaseObj = myObj; 26 myBaseObj.DoSomething(); 27 28 Console.ReadLine(); 29 30 } 31 } 32 }
其結果如下: Base imp
基類方法不必是虛擬的,但結果是一樣的。
調用重寫或隱藏的基類方法
使用base關鍵字,表示包含在派生類中的基類的實現代碼(載控制構造函數時,其用法是類似的)。
因為base使用的是對象實例,所以在靜態成員中使用它會產生錯誤。
還可以使用this關鍵字。與base一樣,this也可以用在類成員的內部,且該關鍵字也引用對象實例。只是this引用的是當前的對象實例(即不能在靜態成員中使用this關鍵字)。
This關鍵字最常用的功能是把當前對象實例的引用傳遞給一個方法,如下例所示:
| public void doSomething() { MyTargetClass myObj=new MyTargetClass(); myObj.DoSomethingWith(this); } |
其中,被實例化的MyTargetClass實例有一個DoSomethingWith()方法,該方法帶一個參數,其類型與包含上述方法的類兼容。這個參數類型可以是類的類型、由這個類繼承的類類型,或者由這個類或System.Object實現的一個接口。
接口成員的定義與類成員的定義相似,但有幾個重要的區別:
- 不允許使用訪問修飾符(public, private, protected 或 internal),所有的接口成員都是公共的。
- 接口成員不能包含代碼體。
- 接口不能定義字段成員。
- 接口成員不能用關鍵字static, virtual, abstract 或 sealed來定義。
- 類型定義成員是禁止的。
可以關鍵字new來隱藏繼承了基接口的成員。例如:
| interface IMyBaseInterface { void DoSomething(); } interface IMyDerivedInterface : IMyBaseInterface { new void DoSomething(); } |
在接口中定義的屬性可以定義訪問塊get和set中的哪一個能用於該屬性(或將它們同時用於該屬性)。
接口可以定義為類的成員(但不能定義為其他接口的成員,因為接口不能包含類型定義)。
可以使用關鍵字virtual或abstract來實現接口成員。
可以使用關鍵字virtual或abstract來實現接口成員,但不能使用static或const。還可以在基類上實現接口成員,例如:
1 public interface IMyInterface 2 { 3 void DoSomething(); 4 void DoSomethingElse(); 5 } 6 7 public class MyBaseClass 8 { 9 public void DoSomething() 10 { 11 } 12 } 13 14 public class MyDerivedClass : MyBaseClass, IMyInterface 15 { 16 public void DoSomethingElse() 17 { 18 } 19 }
繼承一個實現給定接口的基類,就意為着派生類隱式地支持這個接口。
顯示實現接口成員
如果顯示實現接口成員,該成員就只能通過接口來訪問,不能通過類來訪問。隱式成員可以通過類和接口來訪問。
1 public interface IMyInterface 2 { 3 void DoSomething(); 4 void DoSomethingElse(); 5 } 6 7 public class MyClass : IMyInterface 8 { 9 void IMyInterface.DoSomething() 10 { 11 } 12 public void DoSomeThingElse() 13 { 14 } 15 }
其中DoSomething()是顯示實現的,而DoSomethingElse()是隱式實現的。
用非公共的可訪問性添加屬性存儲器
如果實現帶屬性的接口,就必須實現匹配get/set存儲器。這並不是絕對正確——如果在定義屬性的接口中只包含set塊,就可給類中的屬性添加get塊,反之亦然。但是,只有所添加的存儲器的可訪問修飾符比接口中定義的存儲器的可訪問修飾符更嚴格時,才能這么做。因為按照定義,接口定義的存儲器是公共的,也就是說,只能添加非公共的存儲器。
部分類定義(partial class definition)
把類得定義放在多個文件中。為此,只需在每個包含部分類定義的文件中對類使用partial關鍵字既可。例如:
| public partial class MyClass { ...... } |
部分類對Windows應用程序隱藏與窗體布局相關的代碼有很大的作用。在Form1類中,Windows窗體的代碼存儲在Form1.cs和Form1.Designer.cs中。
應用於部分類的接口也會應用於整個類。
部分類定義可以在一個部分類定義文件或者多個部分類定義文件中包含基類。但如果基類在多個定義文件中指定,它就必須是同一個基類,因為在C#中,類只能繼承一個基類。
部分方法定義
部分類也可以定義部分方法。部分方法在部分類中定義,但沒有方法體,在另一個部分類中包含實現代碼。在這兩個部分類中,都要使用partial關鍵字。例如:
| public partial class MyClass { partial void MyPartialMethod(); } public partial class MyClass { partial void MyPartialMethod() { // Method implementation |
部分方法也可以是靜態的,但它們總是私有的,且不能有返回值。他們使用的任何參數都不能是out參數,但可以是ref參數。部分方法也不能使用virtual、abstract、override、sealed和extern修飾符。
實際上,部分方法在編譯代碼時非常重要,其用法倒並不重要。編譯代碼時,如果代碼包含一個沒有實現代碼的部分方法,編譯器會完全刪除該方法,還會刪除對該方法的所有調用。執行代碼時,不會檢查實現代碼,因為沒有檢查方法的調用。這會略微提高性能。
指定項目為解決方案的啟動項目
在Solution Explorer窗口中右擊該項目名,選擇Set as StartUp Project菜單項。
