【一文了解】C#泛型
目录
泛型
1.什么是泛型
为什么需要泛型?
2.类型参数
类型参数的命名规范
3.泛型结构分类
3.1.泛型类(最常用)
3.2.泛型方法
3.3.泛型接口
3.4.泛型委托
4.泛型约束
常用泛型约束
注意
泛型的优点
总结
本篇文章来分享一下泛型,学会使用泛型能大大提升开发的效率。
泛型
1.什么是泛型
在C#中,泛型(Generic)是一种允许定义不依赖于具体数据类型的类、接口、方法或委托的编程特性。它的核心思想是“将类型作为参数传递”,从而实现代码的复用性、类型安全性和性能优化,避免因处理不同类型而编写重复代码(例如,为int、string、自定义类分别编写相似逻辑)。
为什么需要泛型?
解决“重复代码”与“类型安全”问题
在泛型出现之前,处理不同类型的相似逻辑通常有两种方案,但都存在明显缺陷:
1.使用object类型(非类型安全)
object是所有类型的基类,可存储任意类型数据,但需要频繁装箱/拆箱(值类型与object转换),不仅性能损耗大,还会丢失编译时类型检查(例如,将int类型的集合误存入string,编译不报错,运行时才崩溃)。
2.为每种类型写重复代码(低复用性)
若为int、string、Player等每种类型单独编写集合类(如IntList、StringList、PlayerList),逻辑完全相同,仅类型不同,会导致代码冗余、维护成本极高。
泛型的出现完美解决了这两个问题:在编译时确定类型,保证类型安全;同时复用一套逻辑,无需重复编码。
2.类型参数
“类型参数”是泛型的核心语法,泛型的关键是通过<T>(T是“类型参数”的占位符,可自定义名称,如<TItem>、<TData>)声明 “类型变量”,在使用时传入具体类型(如List<int>、Dictionary<string, Player>)。
类型参数的命名规范
通常用单个大写字母表示(如T、TKey、TValue),遵循“见名知意”:
T:通用类型参数(如List<T>);
TKey:键类型(如Dictionary<TKey, TValue>);
TValue:值类型(如Dictionary<TKey, TValue>);
TResult:返回值类型(如Func<TResult>)。
3.泛型结构分类
3.1.泛型类(最常用)
定义泛型类时,在类名后添加<类型参数>,内部可将T作为普通类型使用(如成员变量、方法参数、返回值)。
/// <summary>
/// 泛型类:T 是类型参数,代表“要传入的具体类型”
/// </summary>
public class GenericList<T>
{//用T定义成员变量(存储T类型的数组)private T[] itemArray;private int count;/// <summary>/// 构造函数:初始化数组容量/// </summary>/// <param name="capacity"></param>public GenericList(int capacity){itemArray = new T[capacity];//用T创建数组count = 0;}public void Add(T item)//泛型方法参数为T类型{if (count < itemArray.Length){itemArray[count] = item;//直接存储T类型,无需装箱count++;}}public T Get(int index)//泛型方法:返回值为T类型{if (index >= 0 && index < count){return itemArray[index];//直接返回T类型,无需拆箱}throw new ArgumentOutOfRangeException(nameof(index));}
}
public class Player
{public string name;public Player(string name){this.name = name;}
}
public class Test1:MonoBehaviour
{public void Start(){//1.使用 T 为 int 的泛型类var intList = new GenericList<int>(5);//明确指定T为intintList.Add(10);intList.Add(20);int num = intList.Get(0);//无需强制转换,编译时确认类型安全//2.使用 T 为 Player(自定义类)的泛型类var playerList = new GenericList<Player>(3);playerList.Add(new Player("张三"));Player player = playerList.Get(0); //直接获取Player类型//编译时报错(类型不匹配)intList.Add("abc"); //无法将string存入GenericList<int>}
}
3.2.泛型方法
即使在非泛型类中,也可以定义泛型方法(方法名后加<T>),实现“同一方法处理不同类型”。
public class PrintHelper
{//泛型方法:T是方法的类型参数public void Print<T>(T value){Debug.Log($"值:{value},类型:{typeof(T).Name}");}
}
public class Test2 : MonoBehaviour
{public void Start(){//使用泛型方法PrintHelper helper = new PrintHelper();helper.Print(123);//值:123,类型:Int32helper.Print("Hello");//输出:值:Hello,类型:Stringhelper.Print<Person>(new Person());//输出:值:Player,类型:Player}
}
类型推断调用泛型方法时,若能从参数类型推断出T,可省略<T>,如上述中的helper.Print(123)会自动推断T为int
3.3.泛型接口
接口本质上定义了行为规范,但不实现行为。泛型接口允许在接口定义中使用类型参数,使接口能适配多种数据类型。
/// <summary>
/// 数据操作接口 用于数据的增删改查
/// </summary>
public interface IOperatable<T>
{/// <summary>/// 添加数据/// </summary>/// <param name="data"></param>public void Add(T data);/// <summary>/// 根据ID获取数据/// </summary>/// <param name="id"></param>/// <returns></returns>public T GetById(int id);/// <summary>/// 更新数据/// </summary>/// <param name="data"></param>public void Update(T data);/// <summary>/// 删除数据/// </summary>/// <param name="id"></param>public void Delete(int id);
}
public class User
{public int Id { get; set; }public string Name { get; set; }
}
//实现泛型接口(以用户数据为例)
public class UserOperation : IOperatable<User>
{public void Add(User data){Debug.Log($"添加用户:{data.Name}");}public User GetById(int id){return new User { Id = id, Name = "测试用户" };}public void Update(User data){Debug.Log($"更新用户:{data.Name}");}public void Delete(int id){Debug.Log($"删除用户 ID:{id}");}
}
public class Test3 : MonoBehaviour
{public void Start(){UserOperation user = new UserOperation();user.Add(new User { Id = 1, Name = "测试用户" });user.Update(new User { Id = 2, Name = "测试用户" });Debug.Log(user.GetById(2).Name);user.Delete(2);}
}
3.4.泛型委托
泛型委托允许定义可接受不同类型参数的委托,用于传递具有通用逻辑的方法。
public class Student
{public int Id { get; set; }public string Name { get; set; }public int Age { get; set; }
}
/// <summary>
/// 选择委托
/// 返回数据类型T的属性Tkey的值
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <typeparam name="Tkey">数据类型T的字段</typeparam>
/// <returns>选择的属性</returns>
public delegate TKey SelectHandler<T, TKey>(T t);
public class Test4 : MonoBehaviour
{private void Start(){//int[]int[] intArray = { 4, 1, 5, 0 };SortTestByProperty(intArray, (value) => { return value; });//string[]string[] stringArray = { "2", "a", "ab", "hello", "0" };SortTestByProperty(stringArray, (value) => { return value; });//Student[]Student[] studentArray ={new Student(){ Id=1001,Name="张三",Age=20 },new Student(){ Id=1003,Name="李四",Age=18 },new Student(){ Id=1002,Name="赵六",Age=21 },new Student(){ Id=1000,Name="王五",Age=19 }};SortTestByProperty(studentArray, (studentArray) => { return studentArray.Id; });SortTestByProperty(studentArray, (studentArray) => { return studentArray.Name; });SortTestByProperty(studentArray, (studentArray) => { return studentArray.Age; });}private void SortTestByProperty<T, Tkey>(T[] array, SelectHandler<T, Tkey> selectHandler)where Tkey : IComparable<Tkey>{Debug.Log(array.GetType() + "测试:");PrintSortedArray(array, selectHandler);}private void PrintSortedArray<T, Tkey>(T[] array, SelectHandler<T, Tkey> selectHandler)where Tkey : IComparable<Tkey>{string sortedStr = "";for (int i = 0; i < array.Length; i++){sortedStr += selectHandler(array[i]).ToString() + " ";}Debug.Log(sortedStr);}
}
想要详细了解实现通用的排序功能可以参考【一文读懂】C#如何实现通用的排序功能
4.泛型约束
泛型约束用于限制泛型类型参数的范围,确保类型参数满足特定条件(如继承自某个类、实现某个接口、具有特定构造函数等)。通过约束,可以让泛型代码更安全、更灵活,避免因类型参数不符合预期而导致的运行时错误。
常用泛型约束
约束 | 说明 |
基类约束(where T : 基类名) | 要求类型参数T必须是指定基类或其派生类 |
接口约束(where T : 接口名) | 要求类型参数T必须实现指定接口 |
引用类型约束(where T : class) | 要求类型参数T必须是引用类型(如类、接口、委托等,不能是值类型如int、struct) |
值类型约束(where T : struct) | 要求类型参数T必须是非空值类型(如int、struct、enum,但不能是nullable值类型如int?) |
无参构造函数约束(where T : new()) | 要求类型参数T必须具有公共无参构造函数(包括编译器自动生成的默认构造函数。当与其他约束一起使用时,new()约束必须最后指定 |
泛型参数约束(where T : U) | 要求类型参数T必须是另一个泛型参数U或其派生类型(常用于关联多个泛型参数) |
可单独使用,也可同时指定多个约束(用逗号分隔),实现更精确的限制。
//基类
public class Animal
{ public void Eat() { }
}
//派生类
public class Dog : Animal { }
public class Cat : Animal { }//泛型类:基类约束,约束T必须继承自Animal
public class AnimalCare<T> where T : Animal
{public void Feed(T animal){animal.Eat();}
}
public class Test5:MonoBehaviour
{private void Start(){//使用:正确(Dog/Cat是Animal的子类)var dogCare = new AnimalCare<Dog>();var catCare = new AnimalCare<Cat>();//编译时报错(string不是Animal的子类)var error = new AnimalCare<string>();}
}
注意
1.泛型不是“万能类型”:泛型的T是 “类型参数”,必须在使用时传入具体类型,不能直接使用List<T>(需指定List<int>等)。
2.泛型不支持“值类型默认值直接赋值”:若T可能是值类型(如int),不能直接写T value = null(值类型不能为null),需用default(T)获取默认值(值类型默认0/false,引用类型默认null)。
3.泛型类不能直接重载“基于类型参数的方法”:如void Method<T>(T value)和void Method<int>(int value)不允许,因为泛型实例化后会导致方法签名冲突。
泛型的优点
1.类型安全(编译时检查)
泛型在编译时就确定具体类型,不允许存入不匹配的类型(如List<int>不能存string),避免运行时类型转换错误。
2.性能优化(避免装箱/拆箱)
对于值类型(如int、struct),泛型直接存储原类型,无需像 object 那样进行 “装箱”(值类型转object)和 “拆箱”(object转值类型),减少性能损耗。
3.代码复用(一套逻辑适配多类型)
泛型类/方法只需定义一次,即可处理任意类型(如List<T>可用于int、string、自定义类等),无需为每种类型写重复代码。
4.可读性与维护性强
代码中明确标注类型参数(如List<Player>),语义清晰,后续修改逻辑只需改一处,无需修改所有类型的重复代码,复用性较好。
总结
泛型是C#中提升代码复用性、类型安全性和性能的核心特性,其本质是“类型参数化”。通过<T>定义通用逻辑,使用时传入具体类型,既避免了object型的不安全问题,又能提高代码的复用性。
好了,本次的分享到这里就结束啦,希望对你有所帮助~