当前位置: 首页 > news >正文

c# 泛型的详细介绍

在 C# 中,泛型(Generics) 是一种允许在定义类、接口、方法、委托时使用“未指定类型”(类型参数),在使用时再指定具体类型的技术。它的核心价值是:用一份代码支持多种数据类型,同时保证类型安全(编译时检查),避免传统方法中因使用 object 类型导致的装箱拆箱性能损耗和类型转换错误。

一、为什么需要泛型?(传统方法的问题)

在泛型出现前,若要实现“支持多种类型的通用逻辑”(如集合、工具方法),通常有两种方案,但都存在明显缺陷:

1. 为每种类型写重复代码(代码冗余)

例如,实现一个“整数栈”和“字符串栈”,逻辑完全相同但类型不同,必须重复编码:

// 整数栈
public class IntStack
{private int[] _items = new int[10];private int _index = 0;public void Push(int item) => _items[_index++] = item;public int Pop() => _items[--_index];
}// 字符串栈(与IntStack逻辑相同,仅类型不同)
public class StringStack
{private string[] _items = new string[10];private int _index = 0;public void Push(string item) => _items[_index++] = item;public string Pop() => _items[--_index];
}
2. 使用 object 类型(类型不安全 + 性能损耗)

object 作为通用类型(所有类型的基类),可减少代码冗余,但存在问题:

  • 类型不安全:编译时无法检查类型,运行时可能因类型不匹配报错;
  • 装箱拆箱:值类型(如 int)存入 object 会装箱(堆内存分配),取出时拆箱(类型转换),损耗性能。
// 用object实现的“通用”栈
public class ObjectStack
{private object[] _items = new object[10];private int _index = 0;public void Push(object item) => _items[_index++] = item;public object Pop() => _items[--_index];
}// 使用时的问题
var stack = new ObjectStack();
stack.Push(10);       // int装箱为object
stack.Push("hello");  // 混合存入不同类型(编译不报错)int num = (int)stack.Pop();  // 第二次Pop取出的是"hello",强制转换为int会抛运行时异常!

泛型的出现正是为了解决这些问题:用一份代码支持多种类型,同时保证类型安全(编译时检查),且无需装箱拆箱(值类型直接处理)。

二、泛型的基本用法

泛型通过类型参数(Type Parameter) 实现通用化,语法上用 <T> 表示(T 是类型参数名,可自定义,如 <TItem> <TData>)。

1. 泛型类(Generic Class)

泛型类是最常用的泛型形式,在类定义时声明类型参数,实例化时指定具体类型。

示例:用泛型实现通用栈(解决上述两个问题)

// 泛型栈类:T是类型参数(表示“待指定的类型”)
public class GenericStack<T>  // <T> 声明类型参数
{private T[] _items;  // 用T作为数组元素类型private int _index = 0;// 构造函数:初始化数组大小public GenericStack(int capacity){_items = new T[capacity];  // 创建T类型的数组}// 入栈:参数类型为Tpublic void Push(T item){if (_index < _items.Length)_items[_index++] = item;elsethrow new StackOverflowException("栈已满");}// 出栈:返回类型为Tpublic T Pop(){if (_index > 0)return _items[--_index];elsethrow new InvalidOperationException("栈为空");}
}// 使用泛型类:实例化时指定具体类型(如int、string)
class Program
{static void Main(){// 1. 整数栈(指定T为int)var intStack = new GenericStack<int>(5);  // <int> 确定类型参数intStack.Push(10);intStack.Push(20);int num = intStack.Pop();  // 无需类型转换,直接返回intConsole.WriteLine(num);  // 输出:20// 2. 字符串栈(指定T为string)var strStack = new GenericStack<string>(5);strStack.Push("hello");strStack.Push("world");string str = strStack.Pop();  // 直接返回stringConsole.WriteLine(str);  // 输出:world// 3. 类型安全:编译时检查,不允许混合类型intStack.Push("not int");  // 编译报错!无法将string转换为int}
}

优势

  • 一份代码支持任意类型(intstring、自定义类等);
  • 编译时检查类型,避免运行时错误;
  • 值类型(如 int)无需装箱拆箱,性能更优。
2. 泛型方法(Generic Method)

泛型方法是在方法级别声明类型参数,可独立于泛型类使用(即非泛型类中也可定义泛型方法)。

示例:实现通用的“交换两个变量”方法

public class GenericMethodDemo
{// 泛型方法:<T> 是方法的类型参数public static void Swap<T>(ref T a, ref T b){T temp = a;a = b;b = temp;}
}// 使用泛型方法
class Program
{static void Main(){// 交换intint x = 1, y = 2;GenericMethodDemo.Swap(ref x, ref y);Console.WriteLine($"x={x}, y={y}");  // 输出:x=2, y=1// 交换stringstring s1 = "a", s2 = "b";GenericMethodDemo.Swap(ref s1, ref s2);Console.WriteLine($"s1={s1}, s2={s2}");  // 输出:s1=b, s2=a// 编译时检查:不允许交换不同类型GenericMethodDemo.Swap(ref x, ref s1);  // 编译报错!int和string类型不匹配}
}

说明

  • 泛型方法的类型参数在方法名后声明(Swap<T>);
  • 调用时可省略类型参数(编译器会自动推断,如 Swap(ref x, ref y) 自动推断 Tint)。
3. 泛型接口(Generic Interface)

泛型接口允许接口中的方法、属性使用类型参数,解决非泛型接口的类型安全问题(如 IEnumerableIEnumerable<T>)。

示例:定义通用的“比较”接口

// 泛型比较接口:比较两个T类型的对象
public interface IComparable<T>
{int CompareTo(T other);  // 参数为T类型,避免object转换
}// 实现泛型接口:自定义Person类支持比较年龄
public class Person : IComparable<Person>
{public string Name { get; set; }public int Age { get; set; }// 实现CompareTo:比较当前对象与另一个Person的年龄public int CompareTo(Person other){if (other == null) return 1;  // 自身不为null,比null大return Age.CompareTo(other.Age);  // 利用int的CompareTo}
}// 使用泛型接口
class Program
{static void Main(){Person p1 = new Person { Name = "张三", Age = 20 };Person p2 = new Person { Name = "李四", Age = 25 };int result = p1.CompareTo(p2);Console.WriteLine(result);  // 输出:-1(p1年龄 < p2年龄)}
}

优势

  • 避免非泛型接口(如 IComparable)中参数为 object 导致的类型转换和装箱问题;
  • 编译时确保比较的是同类型对象,更安全。
4. 泛型委托(Generic Delegate)

泛型委托允许委托引用“具有任意类型参数的方法”,.NET 内置了多个常用泛型委托(如 Func<T>Action<T>)。

示例:自定义泛型委托和使用内置泛型委托

// 1. 自定义泛型委托:接收T类型参数,返回void
public delegate void MyAction<T>(T item);// 2. 使用自定义泛型委托
public class DelegateDemo
{public static void PrintInt(int num) => Console.WriteLine($"整数:{num}");public static void PrintString(string str) => Console.WriteLine($"字符串:{str}");
}// 3. 使用.NET内置泛型委托(Func<T, TResult>:有返回值;Action<T>:无返回值)
class Program
{static void Main(){// 自定义泛型委托MyAction<int> intAction = DelegateDemo.PrintInt;intAction(100);  // 输出:整数:100MyAction<string> strAction = DelegateDemo.PrintString;strAction("hello");  // 输出:字符串:hello// 内置Func<T, TResult>:接收int,返回stringFunc<int, string> intToString = num => num.ToString();string result = intToString(123);Console.WriteLine(result);  // 输出:123// 内置Action<T>:接收string,无返回值Action<string> log = msg => Console.WriteLine($"日志:{msg}");log("操作完成");  // 输出:日志:操作完成}
}

三、泛型约束(Generic Constraints)

默认情况下,泛型类型参数 T 可以是任何类型(值类型、引用类型、null等),但有时需要限制 T 的范围(如“只能是引用类型”“必须实现某个接口”),此时需使用泛型约束(通过 where 关键字)。

泛型约束有以下6种常见类型:

1. where T : struct(值类型约束)

限制 T 必须是非可空值类型(如 intDateTime,不能是 string 或自定义类)。

public class ValueTypeDemo<T> where T : struct  // T必须是值类型
{public T GetDefault(){return default(T);  // 值类型的默认值(如int默认0)}
}// 使用
var demo = new ValueTypeDemo<int>();  // 正确:int是值类型
Console.WriteLine(demo.GetDefault());  // 输出:0// var error = new ValueTypeDemo<string>();  // 编译报错:string是引用类型,不满足struct约束
2. where T : class(引用类型约束)

限制 T 必须是引用类型(如 string、自定义类、接口)。

public class ReferenceTypeDemo<T> where T : class  // T必须是引用类型
{public void SetNull(ref T item){item = null;  // 引用类型可赋值为null}
}// 使用
var demo = new ReferenceTypeDemo<string>();  // 正确:string是引用类型
string s = "hello";
demo.SetNull(ref s);
Console.WriteLine(s == null);  // 输出:True// var error = new ReferenceTypeDemo<int>();  // 编译报错:int是值类型,不满足class约束
3. where T : new()(无参构造函数约束)

限制 T 必须有公共无参构造函数(配合其他约束时,new() 必须放在最后)。

public class NewConstraintDemo<T> where T : new()  // T必须有公共无参构造函数
{public T CreateInstance(){return new T();  // 调用无参构造函数创建实例}
}// 符合约束的类(有公共无参构造函数)
public class Person
{public string Name { get; set; }public Person() { }  // 无参构造函数
}// 使用
var demo = new NewConstraintDemo<Person>();
Person p = demo.CreateInstance();  // 成功创建Person实例// 不符合约束的类(无无参构造函数)
public class Student
{public Student(int id) { }  // 只有带参构造函数
}
// var error = new NewConstraintDemo<Student>();  // 编译报错:Student无无参构造函数
4. where T : 基类名(基类约束)

限制 T 必须是“指定基类”或其派生类。

// 基类
public class Animal { public string Name { get; set; } }
// 派生类
public class Dog : Animal { public void Bark() { } }// 基类约束:T必须是Animal或其派生类
public class AnimalDemo<T> where T : Animal
{public void PrintName(T animal){Console.WriteLine(animal.Name);  // 可直接访问基类的属性}
}// 使用
var dogDemo = new AnimalDemo<Dog>();  // 正确:Dog是Animal的派生类
dogDemo.PrintName(new Dog { Name = "旺财" });  // 输出:旺财// var error = new AnimalDemo<string>();  // 编译报错:string不是Animal的派生类
5. where T : 接口名(接口约束)

限制 T 必须实现“指定接口”。

// 接口
public interface IFly { void Fly(); }
// 实现接口的类
public class Bird : IFly { public void Fly() => Console.WriteLine("鸟在飞"); }// 接口约束:T必须实现IFly接口
public class FlyDemo<T> where T : IFly
{public void LetFly(T flyer){flyer.Fly();  // 直接调用接口方法}
}// 使用
var demo = new FlyDemo<Bird>();  // 正确:Bird实现了IFly
demo.LetFly(new Bird());  // 输出:鸟在飞// var error = new FlyDemo<Dog>();  // 编译报错:Dog未实现IFly接口
6. where T : U(另一个类型参数约束)

限制 T 必须是“另一个类型参数 U”或其派生类(用于多类型参数场景)。

public class TypeParamConstraint<T, U> where T : U  // T必须是U或其派生类
{public U Convert(T value){return value;  // T可隐式转换为U}
}// 使用
// 情况1:T=Dog,U=Animal(Dog是Animal的派生类,满足约束)
var demo1 = new TypeParamConstraint<Dog, Animal>();
Animal animal = demo1.Convert(new Dog { Name = "旺财" });// 情况2:T=Animal,U=Dog(Animal是Dog的基类,不满足约束)
// var demo2 = new TypeParamConstraint<Animal, Dog>();  // 编译报错

四、泛型的协变与逆变(高级特性)

在泛型接口和委托中,可通过 out(协变)和 in(逆变)关键字,允许“派生类型向基类型”的隐式转换,提高灵活性。

1. 协变(Covariance):out T

允许将“泛型类型参数为派生类”的接口/委托,隐式转换为“参数为基类”的接口/委托(只读场景,只能返回 T,不能接收 T 作为参数)。

// 协变接口:用out关键字标记T
public interface ICovariant<out T>
{T GetItem();  // 允许返回T(只读)// void SetItem(T item);  // 错误:协变接口不能接收T作为参数(写操作)
}// 实现协变接口
public class CovariantImplementation<T> : ICovariant<T>
{private T _item;public CovariantImplementation(T item) => _item = item;public T GetItem() => _item;
}// 使用协变
class Program
{static void Main(){// 派生类实例(Dog是Animal的派生类)ICovariant<Dog> dogCovariant = new CovariantImplementation<Dog>(new Dog { Name = "旺财" });// 协变:ICovariant<Dog> 可隐式转换为 ICovariant<Animal>ICovariant<Animal> animalCovariant = dogCovariant;Animal animal = animalCovariant.GetItem();  // 正确:返回Dog(是Animal的派生类)Console.WriteLine(animal.Name);  // 输出:旺财}
}

.NET 中典型的协变接口:IEnumerable<out T>(可将 IEnumerable<Dog> 转换为 IEnumerable<Animal>)。

2. 逆变(Contravariance):in T

允许将“泛型类型参数为基类”的接口/委托,隐式转换为“参数为派生类”的接口/委托(只写场景,只能接收 T 作为参数,不能返回 T)。

// 逆变接口:用in关键字标记T
public interface IContravariant<in T>
{void Process(T item);  // 允许接收T作为参数(只写)// T GetItem();  // 错误:逆变接口不能返回T(读操作)
}// 实现逆变接口
public class ContravariantImplementation<T> : IContravariant<T>
{public void Process(T item){Console.WriteLine($"处理{typeof(T).Name}{item.ToString()}");}
}// 使用逆变
class Program
{static void Main(){// 基类实例(Animal是Dog的基类)IContravariant<Animal> animalContravariant = new ContravariantImplementation<Animal>();// 逆变:IContravariant<Animal> 可隐式转换为 IContravariant<Dog>IContravariant<Dog> dogContravariant = animalContravariant;dogContravariant.Process(new Dog { Name = "旺财" });  // 正确:用处理Animal的逻辑处理Dog// 输出:处理Animal:Dog(假设Dog重写了ToString)}
}

.NET 中典型的逆变接口:IComparer<in T>(可将 IComparer<Animal> 转换为 IComparer<Dog>)。

五、泛型的优点总结

  1. 代码复用:一份代码支持多种类型,避免重复开发(如 List<T> 可存储任何类型,无需为每个类型写List)。
  2. 类型安全:编译时检查类型,避免运行时类型转换错误(相比 object 类型)。
  3. 性能优化:值类型使用泛型无需装箱拆箱(直接操作栈内存),减少内存分配和类型转换开销。
  4. 灵活性:通过泛型约束和协变/逆变,在保证安全的同时提高代码灵活性。

总结

泛型是 C# 中核心的类型系统特性,通过类型参数实现了“通用代码+类型安全”的平衡。无论是日常开发中的集合(List<T>Dictionary<TKey, TValue>)、工具方法,还是框架设计(如 EF Core、依赖注入),泛型都无处不在。掌握泛型的用法和约束,能显著提升代码质量和开发效率。

http://www.dtcms.com/a/499532.html

相关文章:

  • OceanBase的SQL和执行计划监控视图
  • 网站原创内容优化wordpress 网站内跳转
  • 龙口市规划建设局网站南京app开发公司排名
  • 解决 Hugging Face 国内下载慢的问题:用 ModelScope 替代加速模型获取
  • 从基础到深入:自然语言处理核心技术全梳理(有 ML/DL 基础)
  • 合肥建设公司网站wordpress 个人电脑
  • 做网站需要哪些方面的支出新媒体运营需要学什么
  • 云手机群控是什么意思
  • 【ecfw】ecfw构建基础
  • 常州二建建设有限公司官方网站聊城做wap网站哪儿好
  • php做网站需要html国外设计公司名字
  • CUDA nvjpeg库编码jpeg图像
  • AI 工作流实战 - 调用豆包api实现批量生图
  • 如何编写您的第一个 Linux 设备驱动程序(一)
  • 做更好的自己 网站客户又找不到你
  • Spring MVC 封装全局统一异常处理
  • 海尔建设网站的内容wordpress设置教程
  • Flutter---EQ均衡器
  • 响应式食品企业网站网站的外链是什么
  • 【Protobuf】proto3语法详解1
  • 网站备案要做家居网站设计
  • VS2022+DirectX9之创建DirectX9设备
  • unordered_map和unordered_set的封装与简单测试
  • (Kotlin协程十六)try/catch 可以捕获子协程的异常吗?为什么?
  • 网站移动端怎么做的做外国网站怎么买空间
  • 图像的脉冲噪声和中值滤波
  • 3.4特殊矩阵的压缩存储
  • SpringAI+DeepSeek大模型应用开发
  • 递归-24.两两交换链表中的节点-力扣(LeetCode)
  • 【Java零基础·第12章】Lambda与Stream API