【一文了解】C#泛型

发布于:2025-09-09 ⋅ 阅读:(22) ⋅ 点赞:(0)

目录

泛型

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为int
        intList.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,类型:Int32
        helper.Print("Hello");//输出:值:Hello,类型:String
        helper.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型的不安全问题,又能提高代码的复用性。

       好了,本次的分享到这里就结束啦,希望对你有所帮助~


网站公告

今日签到

点亮在社区的每一天
去签到