深入理解類
類成員
前兩章闡述了9種類成員中的兩種:字段和方法。本章將會介紹除事件(第14章)和運算符外的其他類成員,並討論其特征。
成員修飾符的順序
字段和方法的聲明可以包括許多如public、private這樣的修飾符。本章還會討論許多其他修飾符。多個修飾符一起使用時,它們需要怎么排序呢?
[特性] [修飾符] 核心聲明
- 修飾符
- 修飾符,必須放在核心聲明前
- 多個修飾符順序任意
- 特性
- 特性,必須放在修飾符和核心聲明前
- 多個特性順序任意
例如,public和static都是修飾符,可以用在一起修飾某個聲明。因為它們都是修飾符,所以順序任意。下面兩行代碼是語義等價的:
public static int MaxVal; static public int MaxVal;
實例類成員
類的每個實例擁有自己的各個類成員的副本,這些成員稱為實例成員。
改變一個實例字段的值不會影響任何其他實例成員中的值。
例
class D { public int Mem1; } class Progarm { static void Main() { D d1=new D(); D d2=new D(); d1.Mem1=10; d2.Mem1=28; Console.WriteLine("d1={0},d2={1}",d1.Mem1,d2.Mem2); } }
靜態字段
除了實例字段,類還可以擁有靜態字段。
- 靜態字段被類的所有實例共享,所有實例訪問同一內存位置。因此,如果該內存位置的值被一個實例改變了,這種改變對所有實例都可見。
- 可以使用static修飾符將字段聲明為靜態
class D { int Mem1; //實例字段 static int Mem2; //靜態字段 }
例:靜態字段演示
- 因為Mem2是靜態的,類D的兩個實例共享單一的Mem2字段。如果Mem2被改變了,這個改變在兩個實例中都能看到
- 成員Mem1沒有聲明為static,所以每個實例都有自己的副本
class D { int Mem1; static int Mem2; ... } static void Main() { D d1=new D(); D d2=new D(); ... }
從類的外部訪問靜態成員
靜態成員可以使用點運算符從類的外部訪問。但因為沒有實例,所以必須使用類名。
類名 ↓ D.Mem2=5; ↑ 成員名
靜態字段示例
- 一個方法設置兩個數據成員的值
- 另一個方法顯示兩個數據成員的值
class D { int Mem1; static int Mem2; public void SetVars(int v1,int v2) { Mem1=v1; Mem2=v2; } public void Display(string str) { Console.WriteLine("{0}:Mem1={1},Mem2={2}",str,Mem1,Mem2); } } class Program { static void Main() { D d1=new D(),d2=new D(); d1.SetVars(2,4); d1.Display("d1"); d2.SetVars(15,17); d2.Display("d2"); d1.Display("d1"); } }
靜態成員的生存期
- 之前我們已經看到了,只有在實例創建后才產生實例成員,實例銷毀后實例成員也就不在存在
- 即使類沒有實例,也存在靜態成員,並可以訪問
class D { int Mem1; static int Mem2; ... } static void Main() { D.Mem2=5; Console.WriteLine("Mem2={0}",D.Mem2); }
字段與類有關,與實例無關
靜態成員即使沒有類的實例也存在。如果靜態字段有初始化語句,那么會在使用該類的任何靜態成員之前初始化該字段,但沒必要在程序執行的開始就初始化。
靜態函數成員
- 如同靜態字段,靜態函數成員獨立於任何類實例。無需類實例就可以調用靜態方法
- 靜態函數成員不能訪問實例成員。只能訪問靜態成員
例:靜態函數與靜態字段
class X { static public int A; static public void PrintValA() { Console.WriteLine("Value of A:{0}",A); } } class Program { static void Main() { X.A=10; X.PrintValA(); } }
其他靜態類成員類型
下表中為可以聲明為static的類成員類型做了√標記
成員常量
成員常量類似本地常量,只是它被聲明在類聲明中而不是方法內。
class MyClass { const int IntVal=100;//定義值為100的int類型常量 } const double PI=3.1416; //錯誤:不能在類型聲明之外
與本地常量類似,初始化成員常量的值在編譯時必須是可計算的。
class MyClass { const int IntVal1=100; const int IntVal2=2*IntVal1;//正確:因為IntVal的值在前一行已設置 }
與本地常量類似,不能在成員常量聲明后給它賦值
class MyClass { const int IntVal//錯誤:聲明時必須初始化 IntVal=100; //錯誤:不允許賦值 }
與C和C++不同,在C#中沒有全局變量。每個常量都必須聲明在類型內。
常量與靜態量
成員常量比本地常量更有趣,因為它們表現得像靜態值。它們對類的每個實例都是“可見的”,而且即使沒有類的實例也可以用。與真正的靜態量不同,常量沒有自己的存儲位置,而是在編譯時被編譯器替換。常量類似C和C++中的#define。
正因為常量在內存沒有存儲位置,所以它也不能作為左值(被賦值)。
static靜態量是有自己的存儲位置的。
例:常量示例
class X { public const double PI=3.1416; } class Program { static void Main() { Console.WriteLine("pi={0}",X.PI); } }
雖然常量成員表現得像一個靜態量,但不能將常量聲明為static
static const double PI=3.14;//錯誤:不能將常量聲明為static
屬性
屬性代表類的實例或類中的一個數據項成員。使用屬性看起來像寫入或讀取一個字段,它們語法相同。
與字段類似,屬性有如下特征
- 它是命名的類成員
- 它有類型
- 它可以被賦值和讀取
然而和字段不同,屬性是一個函數成員
- 它不為數據存儲分配內存
- 它是執行代碼
屬性是指定的一組兩個匹配的、稱為訪問器的方法
- set訪問器為屬性賦值
- get訪問器從屬性獲取
屬性聲明和訪問器
- set訪問器
- 擁有一個單獨的、隱式的值參,名稱為value,與屬性的類型相同
- 擁有一個返回類型void
- get訪問器
- 沒有參數
- 擁有一個與屬性類型相同的返回類型
訪問器的其他重點
- get訪問器的所有執行路徑必須包含一條return語句,返回一個屬性類型的值
- 訪問器set、get順序任意,除這兩個訪問器外在屬性上不允許有其他方法
屬性示例
例:名為C1的類,包含一個名為MyValue的屬性
- 屬性本身沒有任何存儲。訪問器決定如何處理發進來的數據,以及什么數據發出去。示例中,屬性使用TheRealValue字段作為存儲
- set訪問器接受它的輸入參數value,並把值賦給字段TheRealValue
- get訪問器只是返回字段TheRealValue的值
class C1 { private int TheRealValue;//字段:分配內存 public int MyValue //屬性:不分配內存 { set { TheRealValue=value; } get { return TheRealValue; } } }
使用屬性
- 要寫入屬性,在賦值語句的左邊使用屬性名
- 要讀取屬性,把屬性名用在表達式中
- 屬性根據是寫入還是讀取,隱式調用訪問器。(不能顯示調用訪問器)
int MyValue { get{...} set{...} } ... MyValue=5; z=MyValue;
屬性和關聯字段
正如下文C1示例中的。一種常見的方式是在類中將字段聲明為private以封裝字段,並聲明一個public屬性來控制從類的外部對該字段的訪問。
和屬性關聯的字段常被稱為后備字段、后備存儲。
例:使用public的MyValue來控制對private的TheRealValue的訪問
class C1 { private int TheRealValue=10;//字段:分配內存 public int MyValue //屬性:不分配內存 { set{TheRealValue=value;} get{return TheRealValue;} } } class Program { static void Main() { C1 c=new C1(); Console.WriteLine("MyValue:{0}",c.MyValue); c.MyValue=20; Console.WriteLine("MyValue:{0}",c.MyValue); } }
屬性和它后備字段的命名有兩種約定。
約定一:屬性使用Pascal大小寫,字段使用Camel大小寫。雖然這違反了“僅使用大小寫區分不同標識符是壞習慣”。但勝在簡單,有意義。
約定二:屬性使用Pascal大小寫,字段使用Camel大小寫並在開頭加"_"號。
private int firstField; public int FirstField { get{return firstField;} set{firstField=value;} } private int _secondField; public int SecondField { get{return _secondField;} set{_secondField=value;} }
執行其他計算
屬性訪問器不僅對后備字段傳進傳出數據。也可以執行任何計算。
例:通過set屬性訪問器限制Hour的最大值為24
int Hour=12; int MyValue { set { Hour=value>24?24:value; } get { return Hour; } }
上面示例中,演示的條件運算符,將在第8章詳細闡述。
條件運算符是一種三元運算符,計算問號前的表達式,如果表達式結果為true,則返回問號后第一個表達式,否則,返回冒號后的表達式。
只讀和只寫屬性
- 只有get訪問器的屬性稱為只讀屬性。只讀屬性是一種安全的,把一項數據從類或類的實例中傳出,而不允許太多訪問方法
- 只有set訪問器的屬性稱為只寫屬性,只寫屬性是一種安全的,把一項數據從類的外部傳入類,而不允許太多訪問方法
- 兩個訪問器中至少要定義一個
屬性與公共字段
按照推薦的編碼實踐,屬性比公共字段更好
- 屬性是函數型成員而不是數據成員,允許你處理輸入和輸出,而公共字段不行
- 屬性可以只讀或只寫,字段不行
- 編譯后的變量和編譯后的屬性語義不同
計算只讀屬性示例
例:類RightTriangle(直角三角形)的只讀屬性Hypotenuse(斜邊)
- 它有兩個公有字段,表示直角三角形的兩個直角邊長度。這些字段可以被寫入、讀取
- 第三邊由屬性Hypotenuse表示,是只讀屬性,其返回值基於另外兩邊長度
class RightTriangle { public double A=3; public double B=4; public double Hypotenuse { get{return Math.Sqrt((A*A)+(B*B));} } } class Program { static void Main() { var c=new RightTriangle(); Console.WriteLine("Hypotenuse:{0}",c.Hypotenuse); } }
自動實現屬性
因為屬性經常關聯到后備字段,C#提供了自動實現屬性(automatically implemented property),允許只聲明屬性而不聲明后備字段。編譯器為你創建隱藏的后備字段,並且字段掛接到get和set訪問器上。
自動屬性的要點如下
- 不聲明后備字段-編譯器根據屬性類型分配存儲
- 不能提供訪問器的方法體-它們必須被簡單地聲明為分號。get相當於簡單的內存讀,set相當於簡單的寫
- 除非通過訪問器,否則不能訪問后備字段。因為不能用其他方法訪問,所以實現只讀和只寫屬性沒有意義,因此使用自動屬性必須同時提供讀寫訪問器。
例:自動屬性
class C1 { public int MyValue //屬性:分配內存 { set;get; } } class Program { static void Main() { C1 c=new C1(); Console.WriteLine("MyValue:{0}",c.MyValue); c.MyValue=20; Console.WriteLine("MyValue:{0}",c.MyValue); } }
除方便以外,自動屬性使你在傾向於使用公有字段的地方很容易用屬性將其替代。
靜態屬性
屬性也可以聲明為static。靜態屬性的訪問器和靜態成員一樣,具有以下特點
- 不能訪問類的實例成員–它們能被實例成員訪問
- 不管類是否有實例,它們都存在
- 當從類的外部訪問時,必需使用類名引用
例:靜態屬性
class Trivial { public static int MyValue{get;set;} public void PrintValue() { Console.WriteLine("Value from inside:{0}",MyValue); } } class Program { static void Main() { Console.WriteLine("Init Value:{0}",Trival.MyValue); Trival.MyValue=10; Console.WriteLine("New Value:{0}",Trival.MyValue); var tr=new Trivial(); tr.PrintValue(); } }
實例構造函數
實例構造函數是一個特殊的方法,它在創建類的每個新實例時執行。
- 構造函數用於初始化類實例的狀態
- 如果希望從類的外部創建類的實例,需要將構造函數聲明為public
class MyClass { 和類名相同 ↓ public MyClass() { ↑ 沒有返回類型 ... } }
- 構造函數的名稱與類相同
- 構造函數不能有返回值
例:使用構造函數初始化TimeOfInstantiation字段為當前時間
class MyClass { DateTime TimeOfInstantiation; ... public MyClass() { TimeOfInstantiation=DateTime.Now; } ... }
在學完靜態屬性后,我們可以仔細看看初始化TimeOfInstantiation那一行。DateTime類(實際上它是一個結構,但由於還沒介紹結構,你可以先把它當成類)是從BCL中引入的,Now是類DateTime的靜態屬性。Now屬性創建一個新的DateTime類實例,將其初始化為系統時鍾中的當前日期和時間,並返回新DateTime實例的引用。
帶參數的構造函數
- 構造函數可以帶參數。參數語法和其他方法完全相同
- 構造函數可以被重載
例:有3個構造函數的Class
class Class1 { int Id; string Name; public Class1(){Id=28;Name="Nemo";} public Class1(int val){Id=val;Name="Nemo";} public Class1(String name){Name=name;} public void SoundOff() { Console.WriteLine("Name{0},Id{1}",Name,Id); } } class Program { static void Main() { CLass1 a=new Class1(), b=new Class1(7), c=new Class1("Bill"); a.SoundOff(); b.SoundOff(); c.SoundOff(); } }
默認構造函數
如果在類的聲明中沒有顯式的提供實例構造函數,那么編譯器會提供一個隱式的默認構造函數,它有以下特征。
- 沒有參數
- 方法體為空
只要你聲明了構造函數,編譯器就不再提供默認構造函數。
例:顯式聲明了兩個構造函數的Class2
class Class2 { public Class2(int Value){...} public Class2(string Value){...} } class Program { static void Main() { Class2 a=new Class2();//錯誤!沒有無參數的構造函數 ... } }
- 因為已經聲明了構造函數,所以編譯器不提供無參數的默認構造函數
- 在Main中試圖使用無參數的構造函數創建實例,編譯器產生一條錯誤信息
靜態構造函數
實例構造函數初始化類的每個新實例,static構造函數初始化類級別的項。通常,靜態構造函數初始化類的靜態字段。
- 初始化類級別的項
- 在引用任何靜態成員之前
- 在創建類的任何實例之前
- 靜態構造函數在以下方面與實例構造函數類似
- 靜態構造函數的名稱和類名相同
- 構造函數不能返回值
- 靜態構造函數在以下方面和實例構造函數不同
- 靜態構造函數聲明中使用static
- 類只能有一個靜態構造函數,而且不能帶參數
- 靜態構造函數不能有訪問修飾符
class Class1 { static Class1 { ... } }
關於靜態構造函數還有其他要點
- 類既可以有靜態構造函數也可以有實例構造函數
- 如同靜態方法,靜態構造函數不能訪問類的實例成員,因此也不能是一個this訪問器
- 不能從程序中顯式調用靜態構造函數,系統會自動調用它們,在:
- 類的任何實例被創建前
- 類的任何靜態成員被引用前
靜態構造函數示例
class RandomNumberClass { private static Random RandomKey; static RandomNumberClass() { RandomKey=new Random(); } public int GetRandomNumber() { return RandomKey.Next(); } } class Program { static void Main() { var a=new RandomNumberClass(); var b=new RandomNumberClass(); Console.WriteLine("Next Random #:{0}",a.GetRandomNumber()); Console.WriteLine("Next Random #:{0}",b.GetRandomNumber()); } }
對象初始化語句
對象初始化語句擴展了創建語法,允許你在創建新的對象實例時,設置字段和屬性的值。
例:
new Point {X=5,Y=6};
- 創建對象的代碼必須能夠訪問初始化的字段和屬性。如上例中,X和Y必須是public
- 初始化發生在構造方法執行之后,因為構造方法中設置的值可能會在對象初始化中重置為不同的值
public class Point { public int X=1; public int Y=2; } class Program { static void Main() { var pt1=new Point(); var pt2=new Point(X=5,Y=6); Console.WriteLine("pt1:{0},{1}",pt1.X,pt1.Y); Console.WriteLine("pt2:{0},{1}",pt2.X,pt2.Y); } }
析構函數
析構函數(destructor)執行在類的實例被銷毀前需要的清理或釋放非托管資源行為。非托管資源通過Win32 API獲得文件句柄,或非托管內存塊。使用.NET資源無法得到它們,因此如果堅持使用.NET類,就無需為類編寫析構函數。
因此,我們等到第25章再描述析構函數。
readonly修飾符
字段可用readonly修飾。其作用類似於將字段聲明為const,一旦值被設定就不能改變。
- const字段只能在字段聲明語句中初始化,而readonly字段可以在下列任意位置設置它的值
- 字段聲明語句,類似const
- 類的任何構造函數。如果是static字段,初始化必須在靜態構造函數中完成
- const字段的值必須在編譯時決定,而readonly字段值可以在運行時決定。這種增加的自由性允許你在不同環境或構造函數中設置不同的值
- 和const不同,const的行為總是靜態的,而readonly字段有以下兩點
- 它可以是實例字段,也可以是靜態字段
- 它在內存中有存儲位置
例:Shape類,兩個readonly字段
- 字段PI在它的聲明中初始化
- 字段NumberOfSides根據調用的構造函數被設置為3或4
class Shape { readonly double PI=3.1416; readonly int NumberOfSides; public Shape(double side1,double side2) { // 矩形 NumberOfSides=4; ... } public Shape(double side1,double side2,double side3) { // 三角形 NumberOfSides=3; ... } }
this關鍵字
this關鍵字在類中使用,表示對當前實例的引用。它只能被用在下列類成員的代碼塊中。
- 實例構造函數
- 實例方法
- 屬性和索引器的實例訪問器
靜態成員不是實例的一部分,所以不能在靜態函數成員中使用this。換句話說,this用於下列目的:
- 用於區分類的成員和本地變量或參數
- 作為調用方法的實參
例:MyClass類,在方法內使用this關鍵字區分兩個Var1
class MyClass { int Var1=10; public int ReturnMaxSum(int Var1) { 參數 字段 ↓ ↓ return Var1>this.Var1?Var1:this.Var1; } } class Program { static void Main() { var mc=new MyClass(); Console.WriteLine("Max:{0}",mc.ReturnMaxSum(30)); Console.WriteLine("Max:{0}",mc.ReturnMaxSum(5)); } }
索引器
假如我們定義一個Employee類,它帶有3個string型字段,如果不用索引器,我們用字段名訪問它們。
class Employee { public string LastName; public string FirstName; public string CityOfBirth; } class Program { static void Main() { var emp1=new Employee(); emp1.LaseName="Doe"; emp1.FirstName="Jane"; emp1.CityOfBirth="Dallas"; } }
如果能使用索引訪問它們將會很方便,好像該實例是字段的數組一樣。
static void Main() { var emp1=new Employee(); emp1[0]="Doe"; emp1[1]="Jane"; emp1[2]="Dallas"; }
什么是索引器
索引器是一組get和set訪問器,與屬性類似。
索引器和屬性
索引器和屬性在很多方法類似
- 和屬性一樣,索引器不用分配內存來存儲
- 索引器通常表示多個數據成員
可以認為索引器是為類的多個數據成員提供get、set屬性。通過索引器,可以在許多可能的數據成員中進行選擇。索引器本身可以是任何類型。
關於索引器的注意事項
- 和屬性一樣,索引器可以只有一個訪問器,也可以兩個都有
- 索引器總是實例成員,因此不能聲明為static
- 和屬性一樣,實現get、set訪問器的代碼不必一定關聯到某字段或屬性。這段代碼可以什么都不做,只要get訪問器返回某個指定類型值即可
聲明索引器
- 索引器沒有名稱。在名稱的位置,關鍵詞是this
- 參數列表在方括號中
- 參數列表中至少聲明一個參數
Return Type this [Type param1,...] { get{...} set{...} }
聲明索引器類似於聲明屬性。
索引器的set訪問器
當索引器被用於賦值時,set訪問器被調用,並接受兩項數據
- 一個隱式參數,名為value,value持有要保存的數據
- 一個或多個索引參數,表示數據應該保存在哪里
下圖例表明set訪問器有如下語義
- 它的返回類型為void
- 它使用的參數列表和索引器聲明中的相同
- 它有一個名為value的隱式參數,值參類型和索引類型相同
索引器的get訪問器
get訪問器方法體內的代碼必須檢查索引參數,確定它表示哪個字段,並返回字段值。
get訪問器有如下語義
- 它的參數列表和索引器聲明中的相同
- 它返回與索引器相同類型的值
關於索引器的補充
和屬性一樣,不能顯示調用get、set訪問器。取而代之,當索引器用在表達式中取值時,將自動調用get訪問器。索引器被賦值時,自動調用set訪問器。
在“調用”索引器時,要在方括號中提供參數。
索引 值 ↓ ↓ emp[0]="Doe"; //調用set訪問器 string NewName=emp[0]; //調用get訪問器
為Employee示例聲明索引器
下面代碼為示例中的類Employee聲明了一個索引器
- 索引器需要去寫string類型的值,所以string必須聲明為索引器的類型。它必須聲明為public,以便從類外部訪問
- 3個字段被強行索引為整數0-2,所以本例中方括號中間名為index的形參必須為int型
- 在set訪問器方法體內,代碼確定索引指的是哪個字段,並把隱式變量value賦給它。在get訪問器方法體內,代碼確定索引指的哪個字段,並返回該字段的值
class Employee { public string LastName; public string FirstName; public string CityOfBirth; public string this[int index] { set { switch(index) { case 0:LaseName=value; break; case 1:FirstName=value; break; case 2:CityOfBirth=value; break; default: throw new ArgumentOutOfRangeException("index"); } } get { switch(index) { case 0:return LaseName; case 1:return FirstName; case 2:return CityOfBirth; default:throw new ArgumentOutOfRangeException("index"); } } } }
另一個索引器示例
例:為類Class1的兩個int字段設置索引
class Class1 { int Temp0; int Temp1; public int this[int index] { get { return(index==0?Temp0:Temp1;) } set { if(index==0){Temp0=value;} else{Temp1=value;} } } } class Example { static void Main() { var a=new Class1(); Console.WriteLine("Values -- T0:{0},T1:{1}",a[0],a[1]); a[0]=15; a[1]=20; Console.WriteLine("Values--T0:{0},T1:{1}",a[0],a[1]); } }
索引器重載
類可以有任意多個參數列表不同的索引器。(返回類型不同,不是重載)
例:下面示例有3個索引器
class Myclass { public string this[int index] { get{...} set{...} } public string this[int index1,int index2] { get{...} set{...} } public int this[float index1] { get{...} set{...} } }
訪問器的訪問修飾符
本章中,你已看到了兩種帶get、set訪問器的函數成員:屬性和索引器。默認情況下,成員的兩個訪問器的訪問級別和成員自身相同。也就是說,如果一個屬性有public訪問級別,那么它的兩個訪問器也是public的。
不過,你可以為兩個訪問器分配不同訪問級別。例如,下面代碼演示了一個常見且重要的例子–set訪問器聲明為private,get訪問器聲明為public。(get之所以是public,是因為屬性的訪問級別就是public)
注意:在這段代碼中,盡管可以從類的外部讀取該屬性,但卻只能在類的內部設置它。這是非常重要的封裝工具。
class Person { public string Name{get;private set;} public Person(string name) { Name=name; } } class Program { static public void Main() { var p=new Person("Capt,Ernest Evans"); Console.WriteLine("Person's name is {0}",p.Name); } }
訪問器的訪問修飾符有幾個限制。最重要的限制如下。
- 僅當成員(屬性或索引器)既有get訪問器也有set訪問器時,其訪問器才能有訪問修飾符
- 雖然兩個訪問器都必須出現,但它們中只能有一個有訪問修飾符
- 訪問器的訪問修飾符必須比成員的訪問級別有更嚴格的限制性,即訪問器的訪問級別必須比成員的訪問級別低,詳見下圖
例如,如果一個屬性的訪問級別是public,在圖里較低的4個級別中,它的訪問器可以使用任意一個。但如果屬性的訪問級別是protected,則其訪問器唯一能使用的訪問修飾符是private。
分部類和分部類型
類的聲明可以分割成幾個分部類的聲明
- 每個分部類的聲明都含有一些類成員的聲明
- 類的分部類聲明可以在同一文件中也可以在不同文件中
每個局部聲明必須標為partial class,而不是class。分部類聲明看起來和普通類聲明相同。
類型修飾符partial不是關鍵字,所以在其他上下文中,可以把它用作標識符。但直接用在關鍵字class、struct或interface前時,它表示分部類型。
例:分部類
Visual Studio為標准的Windows程序模板使用了這個特性。當你從標准模板創建ASP.NET項目、Windows Forms項目或Windows Persentation Foudation(WPF)項目時,模板為每個Web頁面、表單、WPF窗體創建兩個類文件。
- 一個文件的分部類包含由VS生成的代碼,聲明了頁面上的組件。你不應該修改這個文件中的分部類,因為如果修改頁面組件,VS會重新生成
- 另一個文件包含的分部類可用於實現頁面或表單組件的外觀和行為
- 除了分部類,還有另外兩種分部類型
- 局部結構(第10章)
- 局部接口(第15章)
分部方法
分部方法是聲明在分部類中不同部分的方法。
分部方法的兩個部分如下
- 定義分部方法聲明
- 給出簽名和返回類型
- 聲明的實現部分只是一個分號
- 實現分部方法聲明
- 給出簽名和返回類型
- 是以正常形式的語句塊實現
關於分部方法需要了解的重要內容如下
- 定義聲明和實現聲明的簽名和返回類型必須匹配。簽名和返回類型有如下特征
- 返回類型必須是void
- 簽名不能包括訪問修飾符,這使分部方法是隱式私有的
- 參數列表不能包含out參數
- 在定義聲明和實現聲明中都必須包含上下文關鍵字partial,直接放在關鍵字void前
- 可以有定義部分而沒有實現部分。這種情況下,編譯器把方法的聲明以及方法內部任何對方法的調用都移除。不能只有實現部分而沒有定義部分。
下面是一個名為PrintSum的分部方法的示例
- 因為分部方法是隱式私有的,PrintSum不能從類的外部調用。方法Add是調用PrintSum的公有方法
partial class MyClass { 必須是void ↓ partial void PrintSum(int x,int y);//定義分部方法 public void Add(int x,int y) { PrintSum(x,y); } } partial class MyClass { partial void PrintSum(int x,int y)//實現分部方法 { Console.WriteLine("Sum i {0}",x+y); } } class Program { static void Main() { var mc=new MyClass(); mc.Add(5,6); } }