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);
    }
}


免責聲明!

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



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