C# 類型與成員

C# 為物件導向語言,支援封裝、繼承和多型的概念。 類別可直接繼承自一個父類別,但可以實作數目不拘的介面。 覆寫父類別中虛擬方法的方法需要利用 override 關鍵字,避免意外重複定義。 在 C# 中,結構就像輕量型的類別。其為堆疊配置類型,可以實作介面但不支援繼承。 C# 提供 record classrecord struct 類型,這是主要用途為儲存資料值的類型。

所有類型都是透過建 構函式初始化,這是負責初始化實例的方法。 兩個建構函式宣告具有唯一的行為:

  • 無參數建構函式,會將所有欄位初始化為其預設值。
  • 主要建構函式,這個建構函式會宣告該類型實例的必要參數。

類別與物件

類別是 C# 最基本的型別。 類別是以單一單位結合狀態 (欄位) 和動作 (方法及其他函式成員) 的資料結構。 類別可以為類別的「執行個體」(稱為「物件」) 提供定義。 類別支援「繼承」和「多型」,這些是可供「衍生類別」將「基底類別」延伸及特製化的機制。

建立新類別時,是使用類別宣告來建立。 類別宣告的開頭為標頭。 標頭會指定:

  • 類別的屬性與修飾詞
  • 類別的名稱
  • 基底類別 (從基底類別繼承時)
  • 此類別實作的介面。

此標頭後面會接著類別主體,此主體是由在 {} 分隔符號之間撰寫的成員宣告清單所組成。

下列程式碼顯示名為 Point 之簡單類別的宣告:

public class Point
{
    public int X { get; }
    public int Y { get; }
    
    public Point(int x, int y) => (X, Y) = (x, y);
}

建立類別執行個體時,是使用 new 運算子來建立,此運算子會為新執行個體配置記憶體、叫用建構函式來將執行個體初始化,然後傳回對執行個體的參考。 下列陳述式會建立兩個 Point 物件,並以兩個變數儲存對這些物件的參考:

var p1 = new Point(0, 0);
var p2 = new Point(10, 20);

當物件不再可供存取時,系統會自動回收物件所佔用的記憶體。 在 C# 中,既沒有必要也不可能明確地將物件解除配置。

var p1 = new Point(0, 0);
var p2 = new Point(10, 20);

演算法的應用程式或測試可能需要建立多個 Point 物件。 下列類別會產生隨機點序列。 點數目是由 主要建構函 式參數所設定。 主要建構函式參數 numPoints 位於 類別的所有成員的範圍內:

public class PointFactory(int numberOfPoints)
{
    public IEnumerable<Point> CreatePoints()
    {
        var generator = new Random();
        for (int i = 0; i < numberOfPoints; i++)
        {
            yield return new Point(generator.Next(), generator.Next());
        }
    }
}

您可以使用 類別,如下列程式碼所示:

var factory = new PointFactory(10);
foreach (var point in factory.CreatePoints())
{
    Console.WriteLine($"({point.X}, {point.Y})");
}

型別參數

泛型類別會定義型別參數。 型別參數是以角括弧括住的型別參數名稱清單。 型別參數遵循類別名稱。 接著,就可以在類別宣告的主體中使用這些型別參數,來定義類別的成員。 在下列範例中,Pair 的型別參數是 TFirstTSecond

public class Pair<TFirst, TSecond>
{
    public TFirst First { get; }
    public TSecond Second { get; }
    
    public Pair(TFirst first, TSecond second) => 
        (First, Second) = (first, second);
}

類別型別若宣告為會採用型別參數,即稱為「泛型類別型別」。 結構、介面及委派型別也可以是泛型。 使用泛型類別時,必須為每個型別參數提供型別參數:

var pair = new Pair<int, string>(1, "two");
int i = pair.First;     //TFirst int
string s = pair.Second; //TSecond string

泛型型別若已有提供的型別參數 (如上述的 Pair<int,string>),即稱為「建構的型別」

基底類別

類別宣告可以指定基底類別。 請遵循類別名稱與型別參數,並加上冒號與基底類別的名稱。 省略基底類別規格即等同於衍生自類型 object。 在下列範例中,Point3D 的基底類別是 Point。 在第一個範例中,Point 的基底類別為 object

public class Point3D : Point
{
    public int Z { get; set; }
    
    public Point3D(int x, int y, int z) : base(x, y)
    {
        Z = z;
    }
}

類別會繼承其基底類別的成員。 繼承表示類別隱含其基底類別的所有成員。 類別不會繼承執行個體與靜態建構函式,以及完成項。 衍生類別可以在其繼承的成員中新增成員,但無法移除所繼承成員的定義。 在先前的範例中,Point3D 會從 Point 繼承 XY 成員,而每個 Point3D 執行個體都會包含 XYZ 這三個屬性。

在類別型別到其任何基底類別型別之間都存在著隱含轉換。 類別類型的變數可以參考該類別的執行個體,或任何衍生類別的執行個體。 例如,以先前的類別宣告為例,Point 型別的變數可以參考 PointPoint3D

Point a = new(10, 20);
Point b = new Point3D(10, 20, 30);

結構

類別會定義支援繼承與多型的類型。 其可讓您根據衍生類別的階層來建立複雜行為。 相反地,struct 類型是更簡單的類型,其主要用途是儲存資料值。 結構無法宣告基底類型;其隱含衍生自 System.ValueType。 您無法從 struct 類型衍生其他 struct 類型。 其已隱含密封。

public struct Point
{
    public double X { get; }
    public double Y { get; }
    
    public Point(double x, double y) => (X, Y) = (x, y);
}

介面

interface 定義可由類別與結構實作的合約。 您可以定義 interface 以宣告不同類型之間共用的功能。 例如,System.Collections.Generic.IEnumerable<T> 介面會定義一致的方式來周遊集合中所有項目,例如陣列。 介面可以包含方法、屬性、事件和索引子。 介面通常不提供其所定義成員的實作 (其只會指定必須由類別提供的成員或實作介面的結構)。

介面可以採用多重繼承。 在下列範例中,介面 IComboBox 同時繼承自 ITextBoxIListBox

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

interface IComboBox : ITextBox, IListBox { }

類別和結構可以實作多個介面。 在下列範例中,類別 EditBox 可以實作 IControlIDataBound 兩者。

interface IDataBound
{
    void Bind(Binder b);
}

public class EditBox : IControl, IDataBound
{
    public void Paint() { }
    public void Bind(Binder b) { }
}

當類別或結構實作特定介面時,可以將該類別或結構的執行個體隱含地轉換為該介面型別。 例如:

EditBox editBox = new();
IControl control = editBox;
IDataBound dataBound = editBox;

列舉

Enum 類型會定義一組常數值。 下列 enum 宣告定義不同根莖類蔬菜的常數:

public enum SomeRootVegetable
{
    HorseRadish,
    Radish,
    Turnip
}

您也可以定義組合為旗標所使用的 enum。 下列宣告會宣告一組四季的旗標。 可以套用任何組合的季節,包括內含所有季節的 All 值:

[Flags]
public enum Seasons
{
    None = 0,
    Summer = 1,
    Autumn = 2,
    Winter = 4,
    Spring = 8,
    All = Summer | Autumn | Winter | Spring
}

下列範例顯示上述兩個列舉的宣告:

var turnip = SomeRootVegetable.Turnip;

var spring = Seasons.Spring;
var startingOnEquinox = Seasons.Spring | Seasons.Autumn;
var theYear = Seasons.All;

可為 Null 的類型

任何類型的變數都可以宣告為不可為 Null可為 Null。 可為 Null 的變數可以保存額外的 null 值,表示沒有值。 可為 Null 的實值型別 (struct 或 enum) 會由 System.Nullable<T> 表示。 不可為 Null 與可為 Null 的參考型別都以基礎參考型別表示。 區別則會由編譯器與某些程式庫讀取的中繼資料來表示。 編譯器會在取值可為 Null 的參考時提供警告,而不需要先對 null 檢查其中的值。 編譯器也會在指派不可為 Null 的參考時提供警告,該值可能是 null。 下列範例會宣告可為 Null 的 int,並將其初始化為 null。 然後,其會將值設定為 5。 其會示範與可為 Null 字串相同的概念。 如需詳細資訊,請參閱可為 Null 的實值型別可為 Null 的參考型別

int? optionalInt = default; 
optionalInt = 5;
string? optionalText = default;
optionalText = "Hello World.";

元組

C# 支援 Tuple,其提供精簡語法,將輕量型資料結構中的多個資料元素分組。 您可以宣告 () 之間的成員類型與名稱,將元組具現化,如下列範例所示:

(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
//Output:
//Sum of 3 elements is 4.5.

元組可為具有多個成員的資料結構提供替代方法,而不需使用下一篇文章中所述的建置組塊。