一、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》最多可应用 struct
、class
、class?
、notnull
和 unmanaged
约束中的一个,则它必须是为该类型参数指定的第一个约束; 《2》基类约束(where T : Base
或 where T : Base?
)不能与 struct
、class
、class?
、notnull
或 unmanaged
约束中的任何一个结合使用; 《3》无论哪种形式,都最多只能应用一个基类约束。 如果想要支持可为 null 的基类型,请使用 Base?;
《4》不能将接口不可为 null 和可为 null 的形式命名为约束; 《5》new()
约束不能与 struct
或 unmanaged
约束结合使用。 如果指定 new()
约束,则它必须是该类型参数的最后一个约束。 反约束(如果适用)可以遵循 new()
约束; 《6》default
约束只能应用于替代或显式接口实现。 它不能与 struct
或 class
约束结合使用; 《7》allows ref struct
反约束不能与 class
或 class?
约束结合使用; 《8》allows ref struct
反约束必须遵循该类型参数的所有约束; |||
[泛型的参数类型约束]
二、C#的泛型和匿名类型使用
2.1、泛型类示例
cs
//泛型类定义【基础】
修饰符 class 类名<T>
{
类代码
}
泛型引入了类型参数用来充当数据类型的占位符,如下是一个可用于任何数据类型的泛型类和方法示例:
cs
/// <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、泛型方法示例
cs
//泛型方法定义
修饰符 void 方法名称<类型参数>(类型参数 t)
{
}
//多泛型方法
修饰符 void 方法名称<类型参数1,类型参数2>(类型参数1 left, 类型参数2 Right)
{
}
//带约束的泛型方法
修饰符 类型参数 方法名称<类型参数>(类型参数 left, 类型参数 Right) where 类型参数 : 约束
{
}
cs
//定义一个泛型方法,且实现泛型的数学运算
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、泛型接口示例
cs
//泛型接口定义
修饰符 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的年龄比较排序示例:
cs
--定义两个数据类型比较的接口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 | 协变 | 允许将更具体的类型(派生类型)分配给更常规的类型(基类型) cs //示例【将更具体的string类型转为object类】 IEnumerable<string> strings = new List<string>(); IEnumerable<object> objects = strings;
|
| 2 | 逆变 | 允许将更常规的类型(基类型)分配给更具体的类型(派生类型) cs //示例【将通用的object类型转换为具体的stirng类型】 Action<object> handleObject = obj => Console.WriteLine(obj); Action<string> handleString = handleObject;
|
| 1、使用泛型类型时,协变和逆变允许灵活性,尤其是在将一种类型分配给另一种类型时。 它们有助于确保某些方案中相关类型之间的兼容性。 2、读取数据(如循环访问集合)时,协变非常有用。 在写入或处理数据(如将参数传递给方法)时,逆变非常有用。 协变和逆变 - C# | Microsoft Learn |||
[协变和逆变]
2.4、泛型委托示例
cs
namespace TestConsole
{
//定义泛型委托
public delegate T1 MyDel<T1,T2>(T2 t2);
internal class TestDel
{
public TestDel()
{
//注册委托1方法
notify += MsgNotify;
//注册委托2方法
sendMsg += SendMsg;
}
//声明泛型委托1
public MyDel<string,bool> notify;
//编写泛型委托1的需要调用的方法
private string MsgNotify(bool status)
{
string res = "";
if (status)
{
Console.WriteLine($"---泛型委托1执行:状态是【{status}】---");
res = "执行泛型委托1完成";
}
return res;
}
//声明泛型委托2
public 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;
}
}
}
cs
/// <summary>
/// 测试泛型委托
/// </summary>
private static void TestGenericDelegate()
{
TestConsole.TestDel testDel= new TestConsole.TestDel();
//使用委托1
string res1 = testDel.notify(true);
Console.WriteLine($"使用委托1的结果是【{res1}】\n");
//使用委托2
int res2 = testDel.sendMsg("sg你好,我是客户端");
Console.WriteLine($"使用委托2的结果是【{res2}】\n");
}
执行结果:

2.5、匿名类型示例
|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| 序号 | 匿名类型特点【匿名类型主要用于临时数据结构,定义完整类是不必要的】 |
| 1 | 匿名类型是使用 new
运算符和对象初始值设定项创建的 |
| 2 | 匿名类通常使用隐式类型变量var声明 |
| 3 | 它们通常用于语言集成查询(LINQ)中,以返回对象的部分属性 |
| 注意: 《1》匿名类型允许创建具有只读属性的对象,且不用定义类【编译器为类型生成名称,且该名称在源码中无法访问;其中编译器会自行确定每个属性的类型】; 《2》匿名类型不能用作方法参数或返回类型; 《3》匿名类型只适用于方法范围内创建临时数据结构; ||
[匿名类型特点]
cs
//创建匿名对象
var tmp = new { Name = "匿名类型", msg = "测试" };
Console.WriteLine($"{tmp.Name} {tmp.msg}");
cs
//创建匿名对象数组
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 tmpObjArray
where obj.Price >= 200
select 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