1. 程式人生 > 其它 >C#深入理解類 - C#入門基礎

C#深入理解類 - C#入門基礎

本章包含:

  • 類成員
  • 成員修飾符的順序
  • 例項類成員靜態欄位
  • 從類的外部訪問靜態成員
  • 靜態函式成員
  • 其他靜態類成員型別
  • 成員常量
  • 常量和靜態量
  • 屬性
  • 例項建構函式
  • 靜態建構函式物件初始化語句
  • 解構函式
  • readonly 修飾符
  • this關鍵字
  • 訪問器的訪問修飾符
  • 分部類和分部型別
  • 分部方法

類成員

類成員包括:欄位、常量、方法、屬性、建構函式、解構函式、運算子、索引、事件


修飾符順序

格式:

[特性] [修飾符] 核心宣告

修飾符

  • 如果有修飾符,必須放在核心宣告前
  • 多個修飾符可以任意順序排列;

特性

  • 如果有特性,必須放在修飾符和核心宣告前;
  • 多個特性可以任意順序排列;

特性以後再講,現在先講解修飾符,有:public private protected static async 等;

// 多個修飾符因為可以任意排序,所以下面兩種寫法等價:
public static int MaxVal;
static public int MaxVal;

每個例項成員都是類的一個副本,改變其中一個例項成員不會影響另一個例項成員;

class D{
    public int Mem1;
}

D d1 = new D();
D d2 = new D();
d1.Mem1 = 10; d2.Mem2 = 20;

靜態欄位

靜態欄位被類的所有例項共享,所有例項都訪問同一記憶體位置。因此,如果該記憶體位置的值被一個例項改變了,這種改變對所有的例項都可見。

class D{
    int Mem1;    // 例項欄位
    static int Mem2;    // 靜態欄位
}

D d1 = new D();
D d2 = new D();

通過類訪問:

public class D
{
    static public int AA = 66;
}

class Program
{
    static void Main()
    {
        Console.WriteLine($"{D.AA}");
    }
}

甚至不需要使用類來點出來,而是通過using:

using System;
using System.IO;

using static ConsoleApp2.D;
using static System.Console;
using static System.Math;

namespace ConsoleApp2
{
    public class D
    {
        static public int AA = 66;
    }
    class Program
    {
        static void Main()
        {
            WriteLine($"{AA}, {Sqrt(16)}");
        }
    }
}

靜態成員的生命週期

靜態成員的生命期與例項成員的不同。

即使類沒有例項,也存在靜態成員,並且可以訪問。

上圖中,沒有類例項的靜態成員仍然可以被賦值並讀取,因為靜態欄位與類有關,而與例項無關

即使不存在類例項,靜態成員也存在。
如果靜態欄位有初始化語句,那麼會在使用該類的任何靜態成員之前初始化該欄位,但不一定在程式執行的開始就初始化


靜態函式 成員

與靜態欄位一樣,獨立於任何類的例項,即使沒有類的例項,仍然可以呼叫靜態方法;

但是:靜態方法的方法體中,不能訪問例項成員!但能訪問其他靜態成員;

class X{
    static public int A;
    static public void PrintValA(){
        // 靜態函式中只能訪問靜態成員
        Console.WriteLine(A);
    }
}

X.A = 10;
X.PrintValA();

可以宣告的靜態類成員

可以的資料成員(儲存資料的):欄位、型別

可以的函式成員(執行程式碼的):方法、屬性、建構函式、運算子、事件

不能的:常量、索引器


成員常量

常量是隻讀的;

說白了,常量就是一個其值永遠不會改變的靜態欄位。

常量的值會在編譯時自動推算,編譯器會在遇到常量時,將其逐個替換為該常量的值。

與區域性常量一樣,只是區域性常量初始化在方法體中,而成員常量初始化在類中:

class MyClass{
    const int IntVal = 100;
}

const double PI = 3.1416;   // 錯誤,不能在型別宣告之外宣告

與C/C++ 不同,C#中沒有全域性常量!


靜態只讀變數

在某一個應用程式中初始化之後,不能對其進行修改。但是在不同的應用程式中可以有不同的值。

常量 和 靜態只讀變數 有什麼區別?

常量在所有的程式中都是同一個值,而靜態只讀變數在不同的程式中可以有不同的值。


常量 與 靜態量

成員常量 表現得更像是 靜態值(即:靜態的常/變數),成員常量對類的每個例項都是“可見的”;而且即使沒有類的例項化也可以使用。

與真正的靜態量不同,常量沒有自己的儲存位置,而是在編譯時被編譯器替換。類似C和C++的 #fefine 值。

雖然常量成員表現得像靜態值,但不能將常量宣告為static,如:

static const double PI = 3.14;   // 錯誤,不能將常量宣告為 static

屬性

屬性 和 欄位 很像,用法上他們都幾乎沒區別;

但是,與欄位不同,屬性:

  • 是一個函式成員
  • 不一定為資料分配記憶體
  • 它執行程式碼

屬性本身沒有任何儲存,取而代之,訪問器決定如何處理髮送進來的資料,以及應該將什麼資料傳送出去。在這種情況下,屬性使用一個名為 TheRealValue 的字作為儲存。

class C1 {
    private int theRealValue;      // 欄位:分配記憶體
    
    public int MyValue{        // 屬性:未分配記憶體
        set { theRealValue = value; }
        get { return theRealValue; }
    }
}

屬性的幾種命名規則:

private int firstField;  // Camel駝峰大小寫
public int FirstField{    // Pascal 大小寫
    get { return firstField;}
    set { firstField = value; }
}

private int _firstField;        // 下劃線 和 Camel駝峰大小寫
public int FirstField{
    get { return _firstField;}
    set { _firstField = value; }
}

C#7.0 中為屬性的 getter 和 setter 引入了另一種語法,這語法使用表示式函式體(Lambda):

int MyValue{
    set => value >100 ? 100 : value;
    get => theRealValue;
}

可以省略 set 或 get 中其中一個,如果只有get,則屬性為只讀,反之為只寫;但是不能兩個都省略;


屬性和公有欄位

屬性比公有欄位更好,理由:

  • 屬性是函式成員而不是資料成員,允許你處理輸入和輸出,而公有欄位不行;
  • 屬性可以只讀或只寫,而欄位不行
  • 編譯後的變數和編譯後的屬性語義不同

自動屬性

public int MyValue { get; set;}

public int MyValue2 {get;}

靜態屬性

屬性也能宣告為 static,與其他靜態成員一樣:

  • 不能訪問類的例項成員,但能被例項成員訪問;
  • 不管類是否有例項,它們都是存在的;
  • 在類內部,可以僅使用名稱來引用靜態屬性;
  • 在類外部,可通過類名或者使用 using static 結構來引用靜態屬性;

建構函式


靜態建構函式

呼叫時間:

  • 在引用任何靜態成員之前
  • 在建立類的任何例項之前

與例項建構函式不同:

  • 靜態建構函式使用 static 關鍵字
  • 類只能有一個靜態建構函式,且不能帶引數
  • 靜態建構函式不能有訪問修飾符
class Class1{
    static Class1(){
        // ...
    }
}
  • 類既可以有靜態建構函式,也可以有例項建構函式
  • 靜態建構函式中,不能訪問所在類的例項成員,因此也不能使用 this 訪問器;
  • 不能從程式中顯式地呼叫靜態建構函式,系統會自動呼叫它們:
    • 在類的任何例項被建立之前自動呼叫;
    • 在類的任何靜態成員被引用之前自動呼叫;
class A{
    private static Random RandomKey;
    static A(){
        RandomKey = new Random();
        Console.WriteLine("靜態建構函式");
    }
    
    public A(){
        Console.WriteLine("例項建構函式");
    }

    public int GetRandomNumber(){
        return RandomKey.Next();
    }
}

A a = new A();
A b = new A();

a.GetRandomNumber();
b.GetRandomNumber();


// 輸出:
靜態建構函式
例項建構函式
324130517
例項建構函式
1771231000

物件初始化語句

public class Point{
    public int X = 1;
    public int Y = 2;
}

Point pt1 = new Point();
Point pt12 = new Point { X = 5, Y = 10 };

解構函式

解構函式(destuctor)執行在類的例項被銷燬之前需要的清理或釋放非託管資源的行為。

非託管資源是指通過 Win32 API 獲得的檔案控制代碼,或非託管記憶體塊。

使用 .NET 資源是無法得到它們的,因此如果堅持使用 .NET類,就不需要為類編寫解構函式,

因此,後面再介紹解構函式;


readonly 修飾符

欄位可用 readonly 修飾,一旦值被設定就不能改變。(和 const 非常像吧?)

const欄位只能在欄位的宣告語句中初始化,而 readony欄位可以在下列任意位置設定它的值。

  • 欄位宣告語何,類似於 const
  • 類的任何構造的數。如果是static欄位,初始化必須在靜態建構函式的完成

const欄位的值必須可在編譯時決定,而readonly欄位的值可以在執行時決定,這種自由性允許你在不同的環境或不同的建構函式中設定不同的值!

ccnst的行為總是靜態的,而對於readonly欄位以下兩點是正值的:

  • 它可以是例項欄位,也可以是靜態欄位
  • 它在記憶體中有儲存值

this 關鍵字

this 一般在類中使用,是對當前例項的引用;只能被用在類成員的程式碼塊中:

  • 例項建構函式
  • 例項方法
  • 屬性和索引器的例項訪問器

因為靜態成員不是例項的一部分,所以不能在任何靜態函式成員的程式碼中使用 this 關鍵字;

class A{
    int Var1 = 10;
    public int FuncSum(int Var1){
        return Var1 > this.Var1 ? Var1 : this.Var1;
    }
}

理解它很重要的,但它實際上很少在程式碼中使用。


索引器

沒有索引的簡單類:

有時候,能用索引訪問它們將會很方便,好像該例項是欄位的陣列一樣,這正是索引器能做的事兒;

為 Employee寫一個索引器,看起來像這樣:


什麼是索引器?

是一組 get 和 set 訪問器,與屬性類似。下圖展示了一個類的索引器的表現形式:

索引器和屬性在很多方面是相似的:

  • 和屬性一樣,索引器不用分配記憶體來儲存。
  • 索引器和屬性都主要被用來訪向 其他 資料成員,它們與這些成員關聯,併為它們提供獲取和設定訪問。
    • 屬性通常表示 單個 資料成員
    • 索引器通常表示 多個 資料成員

可以認為索引器是為類的多個數據成員提供get和set訪問的屬性。通過提供索引器,可以在許多可能的資料成員中進行選擇。索引本身可以是任何型別,而不僅僅是資料型別。

關於索引器,還有一些注意事項:

  • 和屬性一樣,索引器可以只有一個訪問器,也可以兩個都有!
  • 索引器總是例項成員,因此不能被宣告為static
  • 和屬性一樣,實現get和set訪問器的程式碼不一定要關聯到某個欄位或屬性。這段程式碼可以做任何事情也可以什麼都不做,只要get訪問器返回某個指定型別的值即可。

宣告索引器

  • 索引器沒有名稱,在名稱的位置是關鍵字 this
  • 引數列表在方括號中間
  • 引數列表中必須至少宣告一個引數。

宣告索引器很類似於宣告屬性:


索引器的 Set 訪問器

索引器被賦值時,Set呼叫;

Set 接收兩個資料:

  • value的隱式引數,為要儲存的資料
  • 一個或多個索引引數,表示資料應該要存在哪裡

emp[0] = "Doe";


索引器的 Get 訪問器

string s =emp[0];


為 Employee 示例宣告索引器

public string this[int index]
{
    set
    {
        switch (index)
        {
            case 0: LastNmae = value; break;
            case 1: FirstName = value; break;
            case 2: CityBirth = value; break;
            default: throw new ArgumentOutOfRangeException("index");
        }
    }

    get
    {
        switch (index)
        {
            case 0: return LastNmae;
            case 1: return FirstName;
            case 2: return CityBirth;
            default: throw new ArgumentOutOfRangeException("index");
        }
    }
}

另一個索引器示例:

class B
{
    int Temp0;
    int Temp1;
    public int this [int index]
    {
        get
        {
            return (0 == index) ? Temp0 : Temp1;
        }
        set
        {
            if( 0 == index)
            {
                Temp0 = value;
            }
            else
            {
                Temp1 = value;
            }
        }
    }
}

B b = new B();
b[0] = 15;
b[1] = 20;

索引器過載

訪問器的訪問修飾符

目前,屬性 和 索引器 成員 都帶有 get 和 set 訪問器;

預設情況下,訪問器的訪問級別和 成員自身相同;

你可以為不同訪問器設定不同的訪問級別;

// get 為public,因為成員自身就是public修飾,而 set設定了private
public string Name { get; private set;}

只有成員同時有 get 和 set 時,才能有訪問修飾符:

public string Name {private set;}  // 錯誤

雖然兩個訪問器要同時出現,但是他們只有一個有訪問修飾符:

public string Name {private get; protected set;}      // 錯誤

訪問器的訪問修飾符的限制必須比成員的訪問級別更加嚴格,即:訪問器的訪問級別必須比成員的訪問級別低;

public string Name { get; public set; }      // 錯誤

分部類 和 分部型別

類的宣告可以分割成幾個分部類的宣告。

  • 每個分部類的宣告都含有一些類成員的宣告
  • 每個分部類可以在同一個檔案中,也可以在不同檔案中。

分部類必須被標註為 partial class ,而不是單獨的 class 關鍵字;

分部類和普通類很一樣,只是分部類用 partial 修飾了:

partial class A1{
    public int t1 {get; set;}
    public int t2 {get; set;}
}

partial class A1{
    public int t3 {get; set;}
    public int t4 {get; set;}
}

注意:partial 不是關鍵字,所以在其他上下文中,可以在程式中把它用作識別符號。但是用在 class、struct 或 interface 之前時,它表示分部型別;