一、C#的泛型簡介
????????泛型是一種允許你延遲編寫類或方法中的數據類型規范,直到你在實際使用時才替換為具體的數據類型【簡單的說:泛型就是允許我們編寫能夠適用于任何數據類型的代碼,而無需為每種特定類型重寫相同的代碼】(T是類型參數,起到站位符的作用,編譯時被真正的類型替代)。
序號 | 泛型的特性說明 |
1 | 泛型有助于開發人員最大限度的重用代碼、保護類型的安全性和提高性能 |
2 | 開發人員可以創建自己的【泛型接口】【泛型類】【泛型方法】【泛型事件】【泛型委托】 |
序號 | 泛型的優點 | 說明 |
1 | 類型安全 | 泛型確保我們在實際代碼中使用的都是正確的數據類型【可在編譯時捕獲到錯誤來確保類型安全】 |
2 | 代碼重用 | 泛型可以讓我們只用編寫一次通用代碼,就可用來處理各種不同數據類型(如各種方法重載) |
3 | 提高性能 | 泛型避免了不必要的類型轉換【沒有裝箱拆箱的消耗】,使得程序運行更快 裝箱和取消裝箱 - C# | Microsoft Learn C#基礎:理解裝箱與拆箱 C# 裝箱和拆箱 |
4 | 靈活性 | 可以創建泛型接口、類、方法和委托內容,可處理我們選擇的任何類型 |
《1》【泛型約束】主要是用于告知編譯器類型參數必須具備的功能(在沒有任何約束的情況下,類型參數可以是任何類型。 編譯器只能假定?System.Object?的成員,它是任何 .NET 類型的最終基類); 《2》使用泛型約束的原因是【約束指定類型參數的功能和預期】(?聲明這些約束意味著你可以使用約束類型的操作和方法調用) | ||
序號 | 泛型約束 | 說明 |
1 | where T : struct | 表示類型參數必須是不可為 null 的值類型;?由于所有值類型都具有可訪問的無參數構造函數(無論是聲明的還是隱式的),因此?struct ?約束表示?new() ?約束,并且不能與?new() ?約束結合使用。?struct ?約束也不能與?unmanaged ?約束結合使用 |
2 | where T : class | 類型參數必須是引用類型。 此約束還應用于任何類、接口、委托或數組類型 |
3 | where T : class? | 類型參數必須是可為 null 或不可為 null 的引用類型。 此約束還應用于任何類、接口、委托或數組類型(包括記錄) |
4 | where T : notnull | 類型參數必須是不可為 null 的類型。 參數可以是不可為 null 的引用類型,也可以是不可為 null 的值類型。 |
5 | where T : unmanaged | 類型參數必須是不可為 null 的非托管類型。?unmanaged ?約束表示?struct ?約束,且不能與?struct ?約束或?new() ?約束結合使用。 |
6 | where T : new() | 類型參數必須具有公共無參數構造函數。 與其他約束一起使用時,new() ?約束必須最后指定。?new() ?約束不能與?struct ?和?unmanaged ?約束結合使用。 |
7 | where T : <基類名> | 類型參數必須是指定的基類或派生自指定的基類。 在可為 null 的上下文中,T ?必須是從指定基類派生的不可為 null 的引用類型。 |
8 | where T : <基類名>? | 類型參數必須是指定的基類或派生自指定的基類。 在可為 null 的上下文中,T ?可以是從指定基類派生的可為 null 或不可為 null 的類型。 |
9 | where T : <接口名> | 類型參數必須是指定的接口或實現指定的接口。 可指定多個接口約束。 約束接口也可以是泛型。 在的可為 null 的上下文中,T ?必須是實現指定接口的不可為 null 的類型 |
10 | where T : <接口名>? | 類型參數必須是指定的接口或實現指定的接口。 可指定多個接口約束。 約束接口也可以是泛型。 在可為 null 的上下文中,T ?可以是可為 null 的引用類型、不可為 null 的引用類型或值類型。?T ?不能是可為 null 的值類型 |
11 | where T : U | 為?T ?提供的類型參數必須是為?U ?提供的參數或派生自為?U ?提供的參數。 在可為 null 的上下文中,如果?U ?是不可為 null 的引用類型,T ?必須是不可為 null 的引用類型。 如果?U ?是可為 null 的引用類型,則?T ?可以是可為 null 的引用類型,也可以是不可為 null 的引用類型 |
12 | where T : default | 重寫方法或提供顯式接口實現時,如果需要指定不受約束的類型參數,此約束可解決歧義。?default ?約束表示基方法,但不包含?class ?或?struct ?約束。 有關詳細信息,請參閱default約束規范建議 |
13 | where T : allows ref struct | 此反約束聲明?T ?的類型參數可以是?ref struct ?類型。 該泛型類型或方法必須遵循?T ?的任何實例的引用安全規則,因為它可能是?ref struct |
某些約束是互斥的,而某些約束必須按指定順序排列: 《1》最多可應用? 《2》基類約束( 《3》無論哪種形式,都最多只能應用一個基類約束。 如果想要支持可為 null 的基類型,請使用? 《4》不能將接口不可為 null 和可為 null 的形式命名為約束; 《5》 《6》 《7》 《8》 |
二、C#的泛型和匿名類型使用
?2.1、泛型類示例
//泛型類定義【基礎】
修飾符 class 類名<T>
{類代碼
}
????????泛型引入了類型參數用來充當數據類型的占位符,如下是一個可用于任何數據類型的泛型類和方法示例:
/// <summary>
/// 箱子泛型類
/// </summary>
/// <typeparam name="T">T是類型參數,可以是C#支持的所有數據類型(如:string,int,double,bool等)</typeparam>
internal class Box<T>
{public T Value { get; set; }public Box(T value){this.Value = value; }public void Print(){Console.WriteLine($"【{Value}】屬于【{Value?.GetType()}】類型");}}//Class_end/// <summary>
/// 測試【箱子泛型類】的部分示例
/// </summary>
private static void TestBox()
{Console.WriteLine("---創建一個存放string數據的箱子---");Box<string> strBox = new Box<string>("字符串箱子");strBox.Print();Console.WriteLine("---創建一個存放Int數據的箱子---");Box<int> intBox = new Box<int>(666);intBox.Print();Console.WriteLine("---創建一個存放Double數據的箱子---");Box<double> doubleBox = new Box<double>(777.88888);doubleBox.Print();}
運行結果如下:【可以看到我們創建的泛型Box類可以創建各種數據類型的內容并打印出來】
2.2、泛型方法示例
//泛型方法定義修飾符 void 方法名稱<類型參數>(類型參數 t){}//多泛型方法修飾符 void 方法名稱<類型參數1,類型參數2>(類型參數1 left, 類型參數2 Right){}//帶約束的泛型方法修飾符 類型參數 方法名稱<類型參數>(類型參數 left, 類型參數 Right) where 類型參數 : 約束{}
//定義一個泛型方法,且實現泛型的數學運算internal class TestMethod{public static T Add<T>(T left, T Right) where T : INumber<T>{return left + Right;}}//Class_end//測試泛型方法private static void Test(){ int res = TestMethod.Add(5,6);Console.WriteLine($"5+6={res}");double res2 = TestMethod.Add(5.55, 6.32);Console.WriteLine($"5.55+6.32={res2}");}
運行結果如下:
2.3、泛型接口示例
//泛型接口定義
修飾符 interface I接口名稱<類型參數>
{類型參數 字段名稱{ get; }void 方法名稱1(類型參數 t); 類型參數 方法名稱2();
}
????????使用泛型接口可在不同的類型中強制實施類型安全行為,且在不同類型之間強制實施一致行為,同時使代碼保持靈活且可重用。
System.Collections.Generic 命名空間(C#所有泛型集合的接口和類) | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/api/system.collections.generic?view=net-9.0????????IComparer<in T>接口表示具體的比較方法;實現創建PeopleComparer繼承IComparer泛型接口對People的年齡比較排序示例:
--定義兩個數據類型比較的接口IComparer
public interface IComparer<in T>
{int Compare(T? x, T? y);
}internal class People {public string? Name { get; set; }public int Age { get; set; }}//Class_end--繼承 IComparer接口并實現具體的比較方法
internal class PeopleComparer : IComparer<People>
{public int Compare(People? x, People? y){return x.Age.CompareTo(y.Age);}
}//Class_end//測試泛型接口
private static void TestGenericInterface()
{var Peoples = new List<People>{new People{Name="張三",Age=26 },new People{Name="李四",Age=29 },new People{Name="王五",Age=28 }};Peoples.Sort(new PeopleComparer());// Peoples.Sort(new PeopleComparer());foreach (var people in Peoples) { Console.WriteLine($"【{people.Name}】【{people.Age}】"); }
}
序號 | 協變和逆變 | 說明 |
1 | 協變 | 允許將更具體的類型(派生類型)分配給更常規的類型(基類型) |
2 | 逆變 | 允許將更常規的類型(基類型)分配給更具體的類型(派生類型) |
1、使用泛型類型時,協變和逆變允許靈活性,尤其是在將一種類型分配給另一種類型時。 它們有助于確保某些方案中相關類型之間的兼容性。 2、讀取數據(如循環訪問集合)時,協變非常有用。 在寫入或處理數據(如將參數傳遞給方法)時,逆變非常有用。 協變和逆變 - C# | Microsoft Learn |
2.4、泛型委托示例
namespace TestConsole
{//定義泛型委托public delegate T1 MyDel<T1,T2>(T2 t2);internal class TestDel{public TestDel(){//注冊委托1方法notify += MsgNotify;//注冊委托2方法sendMsg += SendMsg;}//聲明泛型委托1public MyDel<string,bool> notify;//編寫泛型委托1的需要調用的方法private string MsgNotify(bool status){string res = "";if (status){Console.WriteLine($"---泛型委托1執行:狀態是【{status}】---");res = "執行泛型委托1完成";}return res;}//聲明泛型委托2public MyDel<int, string> sendMsg;//編寫泛型委托2的需要調用的方法private int SendMsg(string msg){int res= 0;if (!string.IsNullOrEmpty(msg) && msg.Contains("sg")){var tmp = msg.Split("sg");Console.WriteLine($"---泛型委托2執行:【{tmp[1]}】開始發送到加密服務器---");res = 222;}return res;}}
}
/// <summary>/// 測試泛型委托/// </summary>private static void TestGenericDelegate(){TestConsole.TestDel testDel= new TestConsole.TestDel();//使用委托1string res1 = testDel.notify(true);Console.WriteLine($"使用委托1的結果是【{res1}】\n");//使用委托2int res2 = testDel.sendMsg("sg你好,我是客戶端");Console.WriteLine($"使用委托2的結果是【{res2}】\n");}
?執行結果:
?2.5、匿名類型示例
序號 | 匿名類型特點【匿名類型主要用于臨時數據結構,定義完整類是不必要的】 |
1 | 匿名類型是使用?new ?運算符和對象初始值設定項創建的 |
2 | 匿名類通常使用隱式類型變量var聲明 |
3 | 它們通常用于語言集成查詢(LINQ)中,以返回對象的部分屬性 |
注意: 《1》匿名類型允許創建具有只讀屬性的對象,且不用定義類【編譯器為類型生成名稱,且該名稱在源碼中無法訪問;其中編譯器會自行確定每個屬性的類型】; 《2》匿名類型不能用作方法參數或返回類型; 《3》匿名類型只適用于方法范圍內創建臨時數據結構; |
//創建匿名對象
var tmp = new { Name = "匿名類型", msg = "測試" };
Console.WriteLine($"{tmp.Name} {tmp.msg}");
//創建匿名對象數組
var tmpObjArray = new[]{new { Name="AB床墊",Price=1600 },new { Name="鼠標",Price=169},new { Name="鍵盤",Price=199 },new { Name="顯示器",Price=699 },};//使用linq語法對內容進行過濾var filterRes = from obj in tmpObjArraywhere obj.Price >= 200select new { obj.Name, obj.Price };foreach (var obj in filterRes){Console.WriteLine($"【{obj.Name}】【{obj.Price}】");}
序號 | 功能 / 特點 | 匿名類型 | 元組類型 |
1 | 類型 | 引用類型 (class ) | 值類型 (struct ) |
2 | 自定義成員名稱 | 支持 | 支持???????? |
3 | 析構 | 不支持 | 支持 |
4 | 表達式樹支持 | 支持 | 不支持 |
在匿名類型和元組類型之間進行選擇 - .NET | Microsoft Learn |
三、參考資料
泛型類型參數 - C# | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/generic-type-parametersNew 約束 - C# reference | Microsoft Learn
https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/new-constraint泛型類和方法 - C# | Microsoft Learn
https://learn.microsoft.com/zh-cn/dotnet/csharp/fundamentals/types/generics委托和事件簡介 - C# | Microsoft Learn
https://learn.microsoft.com/zh-cn/dotnet/csharp/delegates-overview
C# - 泛型:初學者的友好引導 - C# 高級教程 - W3schoolshttps://w3schools.tech/zh-cn/tutorial/csharp/csharp_generics?