以下是對提供的 C# 代碼中涉及的核心知識點的梳理和總結,涵蓋索引器、接口、泛型三大核心內容,以及相關實踐要點:
一、索引器(Indexer)
索引器是一種允許類或結構體像數組一樣通過[]
語法訪問成員的特殊成員,本質是對類中數據的 “索引式訪問” 封裝。
1. 基本定義與格式
作用:讓對象可以通過
對象名[索引]
的方式訪問內部數據(如數組、集合中的元素),簡化訪問邏輯。格式:
public 返回值類型 this[索引類型 索引參數名] {get { /* 獲取數據時執行,返回對應值 */ }set { /* 設置數據時執行,value為賦值內容 */ } }
關鍵說明:
this
關鍵字表示當前對象,索引參數可以是任意類型(int、string 等)。get
塊:通過索引獲取數據時觸發,返回內部存儲的數據。set
塊:通過索引設置數據時觸發,value
是賦值運算符右側的值(若省略set
,則索引器為只讀)。
2. 核心特點
支持多類型索引:同一個類可以定義多個索引器(重載),通過索引參數類型區分。 例如:
ClassRoom
類同時定義this[int index]
(按位置索引)和this[string name]
(按姓名索引)。動態處理邏輯:可在
get/set
中添加自定義邏輯(如邊界檢查、數據轉換)。 例如:索引器練習中,當索引超出數組長度時,動態擴展數組長度。與數組的區別:數組的索引固定為
int
類型且基于連續內存,索引器的索引類型和內部實現可自定義(如基于字典、集合)。
3. 示例解析
在
ClassRoom
類中,通過this[int index]
索引器訪問List<Students>
集合中的元素,get
返回對應索引的學生對象,set
修改對應位置的學生對象。通過
this[string name]
索引器,根據姓名查找學生(使用List.Find
方法),實現按姓名索引的功能。
一、索引器的本質與作用
索引器是 C# 中一種特殊的類成員,允許類或結構的實例像數組一樣通過索引([]
) 進行訪問,從而簡化對內部數據的操作。其核心作用是:將類的內部數據結構(如數組、集合)封裝起來,對外提供類似數組的訪問接口,同時隱藏內部實現細節。
二、索引器的基本語法
// 訪問修飾符 返回值類型 this[參數類型 參數名] public 數據類型 this[索引類型 index] {get { /* 獲取值的邏輯,返回對應數據 */ }set { /* 設置值的邏輯,value表示賦值的內容 */ } }
this
:特殊關鍵字,代表當前類的實例(類似屬性,但索引器沒有名稱,通過this
標識)。索引類型
:可以是任意類型(int、string、自定義類型等),這是索引器與數組的關鍵區別(數組索引只能是 int)。get訪問器
:通過索引獲取值時執行(類似數組的讀操作)。set訪問器
:通過索引設置值時執行(類似數組的寫操作),value
是隱式參數,代表賦值的內容。若省略
set
,則索引器為只讀;若省略get
,則為只寫(通常不推薦)。
三、索引器的重載特性
索引器支持重載(與方法重載規則一致),即同一類中可以定義多個索引器,通過參數類型或參數數量區分。
示例(第一個代碼): ClassRoom
類定義了兩個索引器:
// 1. int類型索引:通過下標訪問學生 public Students this[int index] { get; set; } ? // 2. string類型索引:通過姓名查找學生 public Students this[string n] { get; }
調用時會根據[]
中參數的類型自動匹配對應的索引器:
room[1]; // 匹配int類型索引器 room["鄭爽"]; // 匹配string類型索引器
四、索引器與數組的區別
特性 | 索引器 | 數組 |
---|---|---|
本質 | 類的成員(方法的語法糖) | 引用類型(數據結構) |
索引類型 | 任意類型(int、string 等) | 只能是 int 類型 |
長度靈活性 | 可動態調整(內部邏輯控制) | 長度固定(創建后不可變) |
定義位置 | 類或結構內部 | 獨立定義(變量) |
五、代碼細節分析與擴展
1. 第一個代碼(ClassRoom 類)
內部數據結構:使用
List<Students>
存儲學生,索引器封裝了對 List 的訪問,避免直接暴露 List(封裝性)。string 類型索引器
:通過姓名查找學生,使用
List.Find()
方法結合 Lambda 表達式簡化邏輯:
return students.Find(s => s.Name == n); // 等價于循環遍歷查找,更簡潔
set 訪問器的作用:
this[int index]
的 set 訪問器允許直接通過索引修改 List 中的元素,例如:
room[0] = new Students() { Name = "金秀賢", Sex='女' }; // 實際執行students[0] = value;
2. 第二個代碼(Student 類)
核心功能:索引器處理數組索引越界問題,實現動態擴展數組長度。
關鍵邏輯(set 訪問器):
set {if (index >= names.Length){// 索引越界時,創建新數組并復制原有元素string[] newNames = new string[names.Length + 1];Array.Copy(names, newNames, names.Length); // 復制舊數據newNames[index] = value; // 賦值新元素names = newNames; // 替換舊數組}else{names[index] = value; // 索引正常時直接賦值} }
這個邏輯解決了數組長度固定的問題,通過索引器對外提供 “動態數組” 的體驗。
六、索引器的擴展用法
多參數索引器:支持多個參數(類似二維數組),例如:
// 二維索引器:訪問矩陣中的元素 public int this[int row, int col] {get { return matrix[row, col]; }set { matrix[row, col] = value; } } // 調用:matrix[2, 3] = 10;
限制訪問權限:通過訪問修飾符控制 get/set 的可見性,例如:
public string this[int index] {get { return data[index]; } // 公開可讀private set { data[index] = value; } // 僅類內部可寫 }
結合接口:索引器可以在接口中定義(僅聲明,無實現),由實現類具體實現:
public interface IIndexable {string this[int index] { get; set; } }
總結
索引器是 C# 中增強類交互性的重要特性,通過模擬數組的訪問方式,簡化了對類內部數據的操作。其核心優勢在于:靈活的索引類型、支持重載、可封裝復雜內部邏輯,常用于集合類、數據容器等場景(如List<T>
、Dictionary<TKey, TValue>
內部都實現了索引器)。
二、接口(Interface)
接口是一種規范(“契約”),定義了一組必須實現的成員(屬性、方法等),但不包含實現邏輯,由類或結構體實現。
1. 基本定義與格式
作用:統一不同類的行為標準,實現 “多態” 和 “解耦”。
格式:
interface 接口名(通常以I開頭) {// 成員聲明(無訪問修飾符,默認公開)返回值類型 方法名(參數);類型 屬性名 { get; set; } }
實現規則:類 / 結構體通過
:
實現接口,必須實現接口中所有成員(包括繼承的父接口成員)。
2. 核心特點
多實現:一個類可以實現多個接口(用
,
分隔),解決類的單繼承限制。 例如:Book
類同時實現IBook
和IPaper
接口。接口繼承:接口可以繼承其他接口,子接口包含父接口的所有成員。實現子接口的類必須實現所有父接口和子接口的成員。 例如:
IStudent
繼承IPeople
,Student
類實現IStudent
時,需實現IPeople
的Name
、Age
和IStudent
的StudentId
、Study
。顯式實現:當多個接口包含同名不同類型的成員時,需顯式實現(不添加訪問修飾符,通過 “接口名。成員” 定義)。 例如:
IA
和IB
的C
屬性(int 和 string 類型),通過int IA.C
和string IB.C
實現,訪問時需將對象轉為對應接口類型。
3. 與抽象類的區別
對比項 | 接口 | 抽象類 |
---|---|---|
實現方式 | 類通過: 實現,可多實現 | 類通過: 繼承,僅單繼承 |
成員實現 | 無實現(純規范) | 可包含抽象成員(無實現)和具體成員 |
訪問修飾符 | 成員無修飾符(默認公開) | 成員可加修飾符(public、protected 等) |
成員類型 | 僅屬性、方法、事件、索引器 | 可包含字段、屬性、方法等 |
實例化 | 不能實例化 | 不能實例化 |
一、接口的本質與核心特性
接口是 C# 中一種引用類型,它定義了一組未實現的成員規范(屬性、方法、索引器、事件等),本質是一種 “契約” 或 “規則”。其核心特性包括:
無實現:接口只聲明成員 “是什么”,不定義 “怎么做”(方法無方法體,屬性只有
get/set
聲明)。強制實現:類或結構體實現接口時,必須全部實現接口中的所有成員,否則會編譯錯誤。
多實現支持:一個類 / 結構體可以同時實現多個接口(彌補 C# 類單繼承的限制)。
二、接口的定義語法
// 接口名稱通常以"I"開頭(約定),成員默認是public(不能顯式添加訪問修飾符) interface 接口名 {// 屬性聲明(無實現)返回值類型 屬性名 { get; set; }// 方法聲明(無方法體)返回值類型 方法名(參數列表);// 索引器、事件等(語法類似類成員,但無實現) }
示例(用戶代碼):
interface IBook {string Name { get; set; } ?// 屬性聲明double Price { get; set; }void Fn(); ?// 方法聲明void Fn(string n); ?// 方法重載聲明 }
三、接口的實現
類或結構體通過:
符號實現接口,需嚴格遵循接口規范:
1. 基本實現規則
必須實現接口中所有成員(包括重載的方法、屬性等)。
實現的成員必須與接口聲明的返回值、參數列表、名稱完全一致。
類可以在實現接口的基礎上,添加自己的額外成員(如
Book
類的Color
屬性)。
示例:
class Book : IBook, IPaper ?// 實現多個接口 {// 實現IBook的屬性public string Name { get; set; }public double Price { get; set; }// 實現IPaper的屬性public string Type { get; set; }// 類自己的額外成員public string Color { get; set; }// 實現IBook的方法public void Fn() { /* 具體實現 */ }public void Fn(string n) { /* 具體實現 */ } }
2. 顯式實現(解決成員沖突)
當類實現的多個接口包含同名成員(且類型 / 參數不同)時,需使用顯式實現避免沖突:
語法:
接口名.成員名
(無訪問修飾符)。顯式實現的成員只能通過接口類型的變量訪問,不能通過類實例直接訪問。
示例(用戶代碼):
interface IA { int C { get; set; } } interface IB { string C { get; set; } } ? class Test : IA, IB {// 顯式實現IA的C(int類型)int IA.C { get; set; }// 顯式實現IB的C(string類型)string IB.C { get; set; } } ? // 調用方式 Test test = new Test(); IA ia = test; ia.C = 10; ?// 訪問IA的C ? IB ib = test; ib.C = "hello"; ?// 訪問IB的C
四、接口的繼承
接口支持多繼承(與類不同,類只能單繼承),即一個接口可以繼承多個其他接口,繼承后會包含父接口的所有成員。
規則:
接口繼承語法:
interface 子接口 : 父接口1, 父接口2...
。類實現子接口時,必須同時實現子接口和所有父接口的成員。
示例(用戶代碼):
// IStudent繼承IPeople,包含IPeople的所有成員 interface IStudent : IPeople {string StudentId { get; set; }void Study(); } ? // 實現IStudent必須同時實現IPeople的成員 class Student : IStudent {// 實現IPeople的成員public string Name { get; set; }public int Age { get; set; }// 實現IStudent的成員public string StudentId { get; set; }public void Study() { /* 實現 */ } }
五、接口與抽象類的對比(補充完整)
特性 | 接口 | 抽象類 |
---|---|---|
實例化 | 不能實例化 | 不能實例化 |
成員實現 | 所有成員無實現(純規范) | 可以包含抽象成員(無實現)和非抽象成員(有實現) |
繼承 / 實現方式 | 類 / 結構體通過: 實現,支持多實現 | 類通過: 繼承,僅支持單繼承 |
成員訪問修飾符 | 默認 public,不能顯式添加修飾符 | 可以有 public、protected 等修飾符 |
包含的成員類型 | 只能有屬性、方法、索引器、事件(無字段) | 可以有字段、屬性、方法、索引器、事件等 |
關系本質 | 表示 “具有某種能力”(has-a) | 表示 “是一種”(is-a) |
結構體支持 | 結構體可以實現接口 | 結構體不能繼承抽象類(結構體是值類型) |
六、接口的典型應用場景
定義規范:為不同類提供統一的行為標準(如
ICollection
接口規定集合的基本操作)。多態實現:通過接口類型變量調用不同實現類的方法,實現 “同一接口,不同行為”。
interface IFly { void Fly(); } class Bird : IFly { public void Fly() { Console.WriteLine("鳥飛"); } } class Plane : IFly { public void Fly() { Console.WriteLine("飛機飛"); } } ? // 多態調用 IFly fly1 = new Bird(); IFly fly2 = new Plane(); fly1.Fly(); ?// 輸出"鳥飛" fly2.Fly(); ?// 輸出"飛機飛"
解耦設計:降低類之間的依賴(如依賴注入中,通過接口注入而非具體類)。
總結
接口是 C# 中實現 “規范與實現分離” 的核心機制,通過強制實現、多實現支持、多繼承能力,靈活解決了類單繼承的局限,是實現多態、規范設計的重要工具。理解接口與抽象類的區別,能幫助在不同場景下選擇更合適的設計方式(需要代碼復用選抽象類,需要多能力規范選接口)。
三、泛型(Generic)
泛型是一種 “延遲指定類型” 的語法,允許在定義方法、類、接口時不指定具體類型,而在使用時動態指定,解決代碼復用和類型安全問題。
1. 基本定義與格式
作用:避免為不同類型重復編寫相同邏輯(如 int、string 的通用方法),同時避免裝箱拆箱(提升性能)。
常見形式
:
泛型方法:方法名后加
<T>
,參數或返回值使用T
作為類型。 示例:public static T Fn<T>(T i) { return i; }
泛型接口:接口名后加
<T>
,成員使用T
作為類型。 示例:interface ICalc<T> { T Add(T a, T b); }
泛型類:類名后加
<T>
,成員使用T
作為類型。 示例:class Calc3<T> : ICalc<T> { ... }
2. 核心特點
類型推斷:調用泛型方法時,可省略類型指定(編譯器根據參數自動推斷)。 例如:
Fn(123)
等價于Fn<int>(123)
。多泛型參數:支持多個泛型參數(如
<T1, T2>
),分別指定不同類型。 示例:public static T1 Fn3<T1, T2>(T1 i, T2[] arr) { ... }
默認值:通過
default(T)
獲取泛型類型的默認值(如引用類型為null
,值類型為0
)。性能優勢:相比
object
參數(需裝箱拆箱),泛型直接操作具體類型,減少性能損耗(如泛型測試中,泛型方法比object
參數方法更快)。
3. 泛型約束(補充)
泛型默認支持所有類型,但可通過約束限制T
的范圍(如僅允許引用類型、特定接口的實現類等),語法:where T : 約束條件
。 常見約束:
where T : class
:T
必須是引用類型。where T : struct
:T
必須是值類型。where T : 接口名
:T
必須實現指定接口。where T : 類名
:T
必須是指定類或其派生類。
一、泛型的本質與價值
泛型是 C# 中一種參數化類型的機制,允許在定義類、方法、接口時不指定具體類型,而是在使用時動態指定。其核心價值在于:
代碼復用:一套邏輯適配多種數據類型(避免為 int、string、自定義類型重復編寫相同代碼)。
類型安全:編譯時檢查類型匹配(相比 object 類型轉換,減少運行時錯誤)。
性能優化:避免值類型與引用類型之間的裝箱 / 拆箱操作(見泛型測試代碼分析)。
二、泛型的三種基本形式
1. 泛型方法
在方法名后添加<類型參數>
,調用時指定具體類型(或由編譯器自動推斷)。
語法與特性:
// 定義泛型方法 public static 返回值類型 方法名<T>(T 參數) {// 邏輯實現,T可作為參數類型、返回值類型或局部變量類型 } ? // 調用方式 方法名<int>(123); ? ? ? // 顯式指定類型 方法名("hello"); ? ? ? ?// 隱式推斷類型(T=string)
示例解析(用戶代碼):
public static T Fn<T>(T i) { return i; } // 調用時T被替換為具體類型,等價于: // public static int Fn(int i) { return i; } // public static string Fn(string i) { return i; }
2. 泛型接口
接口定義時包含類型參數,實現接口時需指定具體類型或繼續使用泛型。
語法與特性:
// 定義泛型接口 interface I接口名<T> {T 方法名(T 參數); } ? // 實現方式1:指定具體類型 class 類名 : I接口名<int> {public int 方法名(int 參數) { /* 實現 */ } } ? // 實現方式2:繼續使用泛型(泛型類實現泛型接口) class 類名<T> : I接口名<T> {public T 方法名(T 參數) { /* 實現 */ } }
示例解析(用戶代碼):
// 泛型接口ICalc<T> interface ICalc<T> {T Add(T a, T b);T Sub(T a, T b); } ? // 實現1:指定T=int class Calc : ICalc<int> { /* 實現int類型的加減 */ } ? // 實現2:指定T=string class Calc2 : ICalc<string> { /* 實現string類型的加減 */ }
3. 泛型類
類定義時包含類型參數,實例化時需指定具體類型。
語法與特性:
// 定義泛型類 class 類名<T> {private T 字段;public T 方法(T 參數) { /* 實現 */ } } ? // 實例化 var 變量 = new 類名<int>(); ?// T被替換為int
示例解析(用戶代碼):
class Calc3<T> : ICalc<T> {public T Add(T a, T b){return default(T); ?// default(T)返回T類型的默認值} } ? // 使用時指定類型 var calc = new Calc3<double>(); double result = calc.Add(1.5, 2.5); ?// result=0.0(double默認值)
三、泛型的性能優勢(基于測試代碼)
用戶提供的_08_泛型測試
代碼通過計時器對比了三種方式的性能:
方法類型 | 實現方式 | 10000 次調用耗時(示例值) | 性能差異原因 |
---|---|---|---|
ShowInt | 具體類型(int) | 187ms | 無類型轉換,直接操作 |
ShowObject | object 類型(裝箱 / 拆箱) | 235ms | int→object(裝箱)和 object→int(拆箱)消耗性能 |
Show<T> | 泛型方法 | 220ms | 編譯時生成具體類型代碼,無裝箱 / 拆箱 |
結論:泛型性能接近具體類型方法,遠優于 object 類型(避免了值類型與引用類型轉換的開銷)。
四、泛型的關鍵特性
類型推斷 調用泛型方法時,若編譯器可從參數推斷出類型,可省略
<類型>
:Fn2(1, new int[] { 1 }); ?// 推斷T=int Fn3(1, new string[] { "a" }); ?// 推斷TTest1=int,TTest2=string
多類型參數 泛型可包含多個類型參數(用
,
分隔):public static T1 Fn3<T1, T2>(T1 i, T2[] arr) { return i; }
默認值(default (T)) 用于獲取任意類型的默認值(值類型為
0
/false
等,引用類型為null
):return default(T); ?// 泛型中安全獲取默認值
五、泛型與其他技術的對比
技術 | 優勢 | 劣勢 | 適用場景 |
---|---|---|---|
泛型 | 類型安全、性能好、代碼復用 | 語法稍復雜 | 通用邏輯(如集合、工具類) |
重載方法 | 簡單直觀 | 類型增多時代碼量爆炸 | 類型較少的場景 |
object 類型 | 靈活(支持所有類型) | 性能差(裝箱)、類型不安全(需強制轉換) | 早期版本 C#(無泛型時) |
總結
泛型是 C# 中實現 “編寫一次,適配多類型” 的核心機制,通過泛型方法、泛型類、泛型接口三種形式,在保證類型安全和性能的前提下,極大提升了代碼復用率。其設計思想貫穿于.NET Framework 的核心組件(如List<T>
、Dictionary<TKey, TValue>
),是 C# 開發者必須掌握的重要特性。