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 之前時,它表示分部型別;
完