C# 類型與成員
C# 為物件導向語言,支援封裝、繼承和多型的概念。 類別可直接繼承自一個父類別,但可以實作數目不拘的介面。 覆寫父類別中虛擬方法的方法需要利用 override
關鍵字,避免意外重複定義。 在 C# 中,結構就像輕量型的類別。其為堆疊配置類型,可以實作介面但不支援繼承。 C# 提供 record class
與 record 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
的型別參數是 TFirst
和 TSecond
:
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
繼承 X
與 Y
成員,而每個 Point3D
執行個體都會包含 X
、Y
與 Z
這三個屬性。
在類別型別到其任何基底類別型別之間都存在著隱含轉換。 類別類型的變數可以參考該類別的執行個體,或任何衍生類別的執行個體。 例如,以先前的類別宣告為例,Point
型別的變數可以參考 Point
或 Point3D
:
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
同時繼承自 ITextBox
和 IListBox
。
interface IControl
{
void Paint();
}
interface ITextBox : IControl
{
void SetText(string text);
}
interface IListBox : IControl
{
void SetItems(string[] items);
}
interface IComboBox : ITextBox, IListBox { }
類別和結構可以實作多個介面。 在下列範例中,類別 EditBox
可以實作 IControl
與 IDataBound
兩者。
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.
元組可為具有多個成員的資料結構提供替代方法,而不需使用下一篇文章中所述的建置組塊。