1. 程式人生 > >常量、欄位、方法、屬性和索引器(C#)

常量、欄位、方法、屬性和索引器(C#)

常量(Constants)

常量是在編譯時已知並在程式的生存期內不發生更改的不可變值。 常量使用 const 修飾符進行宣告。 只有 C# 內建型別(System.Object 除外)可以宣告為 const。使用者定義的型別(包括類、結構和陣列)不能為 const。 請使用 readonly 修飾符建立在執行時初始化一次即不可再更改的類、結構或陣列。
注:內建型別表如下

這裡寫圖片描述

常量必須在宣告時初始化。例如:
C#:

class Calendar1
{
    public const int months = 12;
}

在此示例中,常量 months 始終為 12,不可更改,即使是該類自身也不能更改它。 實際上,當編譯器遇到 C# 原始碼(例如 months)中的常量修飾符時,將直接把文字值替換到它生成的中間語言 (IL) 程式碼中。 因為在執行時沒有與常量關聯的變數地址,所以 const 欄位不能通過引用傳遞,並且不能在表示式中作為左值出現。

欄位(fields )

“欄位”是直接在類或結構中宣告的任何型別的變數。 欄位是其包含型別的“成員”。

類或結構可以擁有例項欄位(instance fields)靜態欄位( static fields ),或同時擁有兩者。 例項欄位特定於型別的例項。 如果您擁有類 T 和例項欄位 F,可以建立型別 T 的兩個物件,並修改每個物件中 F 的值,這不影響另一物件中的該值。 相比之下,靜態欄位屬於類本身,在該類的所有例項中共享。 從例項 A 所做的更改將立刻呈現在例項 B 和 C 上(如果它們訪問該欄位)。

通常應僅為具有私有或受保護可訪問性的變數使用欄位。 您的類向客戶端程式碼公開的資料應通過方法、屬性和索引器提供。 通過使用這些構造間接訪問內部欄位,可以針對無效的輸入值提供防護。 儲存由公共屬性公開的資料的私有欄位稱為“後備儲存”或“支援欄位”。

  • 在類塊中通過指定欄位的訪問級別,然後指定欄位的型別,再指定欄位的名稱來宣告這些欄位。 例如:
    C#:
public class CalendarEntry
{
    // private field
    private DateTime date;

    // public field (Generally not recommended.)
    public string day;
    ...
}
  • 若要訪問物件中的欄位,請在物件名稱後面新增一個句點,然後新增該欄位的名稱,比如 objectname.fieldname。 例如:
    C#:
CalendarEntry birthday = new
CalendarEntry(); birthday.day = "Saturday";
  • 宣告欄位時可以使用賦值運算子為欄位指定一個初始值。 例如,若要自動將 “Monday” 賦給 day 欄位,需要宣告 day,如下例所示:
    C#:
public class CalendarDateWithInitialization
{
    public string day = "Monday";
    //...
}
  • 欄位可標記為 public、private、protected、internal 或 protected internal。 這些訪問修飾符定義類的使用者訪問欄位的方式。
    注:訪問修飾符

這裡寫圖片描述

  • 可以選擇將欄位宣告為 static。 這使得呼叫方在任何時候都能使用欄位,即使類沒有任何例項。

  • 可以將欄位宣告為 readonly。 只讀欄位只能在初始化期間或在建構函式中賦值。 static readonly 欄位非常類似於常數,只不過 C# 編譯器不能在編譯時訪問靜態只讀欄位的值,而只能在執行時訪問。

注:變數與欄位的區別與聯絡

public class Test
{
    // field在這裡是一個欄位變數
    public int field =0;
    // 靜態欄位,
    public static int filed2 =1;

    public void TestMethod()
    {
        // 此時a是一個變數,並且是一個區域性變數,a就不是欄位了
        // 而對於field 和field2我們最好認為其是一個欄位,但是它也是變數,我們一般叫法為 “欄位變數”
        int a =2;
        Console.Write(a);
    }
}

方法(method)

方法是包含一系列語句的程式碼塊。 程式通過呼叫該方法並指定任何所需的方法引數使語句得以執行。 在 C# 中,每個執行的指令均在方法的上下文中執行。 Main 方法是每個 C# 應用程式的入口點,並在啟動程式時由公共語言執行時 (CLR) 呼叫。

方法簽名

  • 通過指定訪問級別(如 public 或 private)、可選修飾符(如 abstract 或 sealed)、返回值、方法的名稱以及任何方法引數,在類或結構中宣告方法。 這些部件一起構成方法的簽名。
  • 方法引數在括號內,並且用逗號分隔。 空括號指示方法不需要任何引數。

注:出於方法過載的目的,方法的返回型別不是方法簽名的一部分。 但是在確定委託和它所指向的方法之間的相容性時,它是方法簽名的一部分。

補充:方法過載是指在一個類中定義多個同名的方法,但要求每個方法具有不同的引數的型別或引數的個數。比如:

   public void Func(){}
   public void Func(int a){}
   public void Func(int a,int b){}
   ···
   Func(a);//會呼叫public void Func(int a)這個版本

具體規範:

  • 方法名一定要相同。
  • 方法的引數表必須不同,包括引數的型別或個數,以此區分不同的方法體。(如果引數個數不同,就不管它的引數型別了。如果引數個數相同,那麼引數的型別或者引數的順序必須不同。)
  • 方法的返回型別、修飾符可以相同,也可不同。即在C#中不能只根據返回值的型別不同來區分不同的過載函式。

方法訪問

呼叫物件上的方法就像訪問欄位。 在物件名之後新增一個句點、方法名和括號。 引數列在括號裡,並且用逗號分隔。

方法引數與引數

該方法定義指定任何所需引數的名稱和型別。呼叫程式碼呼叫該方法時,它為每個引數提供了稱為引數的具體值。 引數必須與引數型別相容,但呼叫程式碼中使用的引數名(如果有)不需要與方法中定義的引數名相同。

按引用傳遞與按值傳遞

預設情況下,值型別傳遞給方法時,傳遞的是副本而不是物件本身。 因此,對引數的更改不會影響呼叫方法中的原始副本。可以使用 ref 關鍵字按引用傳遞值型別。

引用型別的物件傳遞到方法中時,將傳遞對物件的引用。 也就是說,該方法接收的不是物件本身,而是指示該物件位置的引數。 如果通過使用此引用更改物件的成員,即使是按值傳遞該物件,此更改也會反映在呼叫方法的引數中。

通過使用 class 關鍵字建立引用型別,如以下示例所示。

public class SampleRefType
{
    public int value;
}

現在,如果將基於此型別的物件傳遞到方法,則將傳遞對物件的引用。 下面的示例將 SampleRefType 型別的物件傳遞到 ModifyObject 方法。

public static void TestRefType()
{
    SampleRefType rt = new SampleRefType();
    rt.value = 44;
    ModifyObject(rt);
    Console.WriteLine(rt.value);
}
static void ModifyObject(SampleRefType obj)
{
    obj.value = 33;
}

該示例執行的內容實質上與先前示例相同,均按值將引數傳遞到方法。 但是因為使用了引用型別,結果有所不同。 ModifyObject 中所做的對形參 obj 的 value 欄位的修改,也會更改 TestRefType 方法中實參 rt 的 value 欄位。 TestRefType 方法顯示 33 作為輸出。

返回值

方法可以將值返回到呼叫方。如果列在方法名之前的返回型別不是 void,則該方法可通過使用 return 關鍵字返回值。帶 return 關鍵字,後跟與返回型別匹配的值的語句將該值返回到方法呼叫方。return 關鍵字還會停止執行該方法。如果返回型別為 void,沒有值的 return 語句仍可用於停止執行該方法。 沒有 return 關鍵字,當方法到達程式碼塊結尾時,將停止執行。具有非空的返回型別的方法都需要使用 return 關鍵字來返回值。例如,這兩種方法都使用 return 關鍵字來返回整數:

class SimpleMath
{
    public int AddTwoNumbers(int number1, int number2)
    {
        return number1 + number2;
    }

    public int SquareANumber(int number)
    {
        return number * number;
    }
}

若要使用從方法返回的值,呼叫方法可以在相同型別的值足夠的地方使用該方法呼叫本身。 也可以將返回值分配給變數。 例如,以下兩個程式碼示例實現了相同的目標:

int result = obj.AddTwoNumbers(1, 2);
result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);
result = obj.SquareANumber(obj.AddTwoNumbers(1, 2));
// The result is 9.
Console.WriteLine(result);

屬性(property)

屬性是一種成員,它提供靈活的機制來讀取、寫入或計算私有欄位的值。 屬性可用作公共資料成員,但它們實際上是稱為“訪問器”的特殊方法。 這使得可以輕鬆訪問資料,還有助於提高方法的安全性和靈活性。

屬性結合了欄位和方法的多個方面。 對於物件的使用者,屬性顯示為欄位,訪問該屬性需要相同的語法。 對於類的實現者,屬性是一個或兩個程式碼塊,表示一個 get 訪問器和/或一個 set 訪問器。 當讀取屬性時,執行 get 訪問器的程式碼塊;當向屬性分配一個新值時,執行 set 訪問器的程式碼塊。 不具有 set 訪問器的屬性被視為只讀屬性。 不具有 get 訪問器的屬性被視為只寫屬性。 同時具有這兩個訪問器的屬性是讀寫屬性。

與欄位不同,屬性不作為變數來分類。 因此,不能將屬性作為 ref(C# 參考)引數或 out(C# 參考)引數傳遞。

屬性具有多種用法:它們可在允許更改前驗證資料;它們可透明地公開某個類上的資料,該類的資料實際上是從其他源(例如資料庫)檢索到的;當資料被更改時,它們可採取行動,例如引發事件或更改其他欄位的值。

屬性在類塊中是按以下方式來宣告的:指定欄位的訪問級別,接下來指定屬性的型別和名稱,然後跟上宣告 get 訪問器和/或 set 訪問器的程式碼塊。 例如:

public class Date
{
    private int month = 7;  // Backing store

    public int Month
    {
        get
        {
            return month;
        }
        set
        {
            if ((value > 0) && (value < 13))
            {
                month = value;
            }
        }
    }
}

在此示例中,Month 是作為屬性宣告的,這樣 set 訪問器可確保 Month 值設定為 1 和 12 之間。 Month 屬性使用私有欄位來跟蹤該實際值。 屬性的資料的真實位置經常稱為屬性的“後備儲存”。屬性使用作為後備儲存的私有欄位是很常見的。 將欄位標記為私有可確保該欄位只能通過呼叫屬性來更改。

get 訪問器

get 訪問器體與方法體相似。 它必須返回屬性型別的值。 執行 get 訪問器相當於讀取欄位的值。

以下是返回私有欄位 name 的值的 get 訪問器:

class Person
{
    private string name;  // the name field
    public string Name    // the Name property
    {
        get
        {
            return name;
        }
    }
}

當引用屬性時,除非該屬性為賦值目標,否則將呼叫 get 訪問器以讀取該屬性的值。 例如:

Person person = new Person();
//...

System.Console.Write(person.Name);  // the get accessor is invoked here

get 訪問器必須以 return 或 throw 語句終止,並且控制權不能離開訪問器體。

通過使用 get 訪問器更改物件的狀態不是一種好的程式設計風格。 例如,以下訪問器在每次訪問 number 欄位時都會產生更改物件狀態的副作用。

private int number;
public int Number
{
    get
    {
        return number++;   // Don't do this
    }
}

get 訪問器可用於返回欄位值,或用於計算並返回欄位值。 例如:

class Employee
{
    private string name;
    public string Name
    {
        get
        {
            return name != null ? name : "NA";
        }
    }
}

在上一個程式碼段中,如果不對 Name 屬性賦值,它將返回值 NA。

set 訪問器

set 訪問器類似於返回型別為 void 的方法。 它使用稱為 value 的隱式引數,此引數的型別是屬性的型別。 在下面的示例中,將 set 訪問器新增到 Name 屬性:

class Person
{
    private string name;  // the name field
    public string Name    // the Name property
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
}

當對屬性賦值時,用提供新值的引數呼叫 set 訪問器。 例如:

Person person = new Person();
person.Name = "Joe";  // the set accessor is invoked here                

System.Console.Write(person.Name);  // the get accessor is invoked here

在 set 訪問器中,對區域性變數宣告使用隱式引數名稱 value 是錯誤的。

備註

  • 可將屬性標記為 public、private、protected、internal 或 protected internal。 這些訪問修飾符定義類的使用者如何才能訪問屬性。 同一屬性的 get 和 set 訪問器可能具有不同的訪問修飾符。 例如,get 可能是 public 以允許來自型別外的只讀訪問;set 可能是 private 或 protected。 有關更多資訊,請參見 訪問修飾符(C# 程式設計指南)
  • 可以使用 static 關鍵字將屬性宣告為靜態屬性。 這使得呼叫方隨時可使用該屬性,即使不存在類的例項。 有關更多資訊,請參見 靜態類和靜態類成員(C# 程式設計指南)
  • 可以使用 virtual 關鍵字將屬性標記為虛屬性。 這樣,派生類就可以通過使用 override 關鍵字來重寫事件行為。 有關這些選項的更多資訊,請參見 繼承(C# 程式設計指南)
  • 重寫虛屬性的屬性還可以是 sealed 的,這表示它對派生類不再是虛擬的。 最後,可以將屬性宣告為 abstract。 這意味著類中沒有任何實現,派生類必須編寫自己的實現。 有關這些選項的更多資訊,請參見 抽象類、密封類及類成員(C# 程式設計指南)
  • 對 static 屬性的訪問器使用 virtual、abstract或 override修飾符是錯誤的。

索引器(Indexers)

  • 索引器允許類或結構的例項就像陣列一樣進行索引。 索引器類似於屬性,不同之處在於它們的取值函式採用引數。
  • 使用索引器可以用類似於陣列的方式為物件建立索引。
  • get 取值函式返回值。 set 取值函式分配值。
  • this 關鍵字用於定義索引器。
  • value 關鍵字用於定義由 set 索引器分配的值。
  • 索引器不必根據整數值進行索引;由你決定如何定義 特定的查詢機制。
  • 索引器可被過載。
  • 索引器可以有多個形參,例如當訪問二維陣列時。

在下面的示例中,定義了一個泛型類,併為其提供了簡單的 get 和 set 取值函式方法(作為分配和檢索值的方法)。 Program 類建立了此類的一個例項,用於儲存字串。

class SampleCollection<T>
{
    // Declare an array to store the data elements.
    private T[] arr = new T[100];

    // Define the indexer, which will allow client code
    // to use [] notation on the class instance itself.
    // (See line 2 of code in Main below.)        
    public T this[int i]
    {
        get
        {
            // This indexer is very simple, and just returns or sets
            // the corresponding element from the internal array.
            return arr[i];
        }
        set
        {
            arr[i] = value;
        }
    }
}

// This class shows how client code uses the indexer.
class Program
{
    static void Main(string[] args)
    {
        // Declare an instance of the SampleCollection type.
        SampleCollection<string> stringCollection = new SampleCollection<string>();

        // Use [] notation on the type.
        stringCollection[0] = "Hello, World";
        System.Console.WriteLine(stringCollection[0]);
    }
}
// Output:
// Hello, World.

參考資料