1. 程式人生 > >C#圖解教程 第六章 深入理解類

C#圖解教程 第六章 深入理解類

深入理解類

類成員


前兩章闡述了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);
    }
}

 

from: http://www.cnblogs.com/moonache/p/6097402.html