在 C# 中,泛型是一项强大的特性,它允许你创建通用的类、接口、方法、委托等,这些通用的结构可以处理多种不同的数据类型,而无需为每种类型编写重复的代码。
目录
1.泛型
什么是泛型?为什么要用泛型?泛型有什么优点?
泛型(generic)是专门是为了处理多端代码不同数据类型上执行相同的指令而设计。
优点:
a.速度快且支持不同类型
b.提高代码复用性
c.性能高
d.语法优美简洁
e.保护类型的安全。
泛型如何定义?
泛型定义格式:<T>或<T,K,......> 其中T,K指未知类型。 T,K,M,N
泛型定义时,是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到了调用的时候才指定参数类型。
#泛型类 也可有多个未知的数据类型,中间用逗号隔开
public class 类名<T>(){
代码块
}
#泛型方法
public static void 方法名<T>(){
代码块
}
泛型运行原理?
程序在执行时,会进行编译,生成二进制代码,之后才被执行。
在使用泛型以后,编译到泛型时,会先生成占位符,之后会把占位符替换成具体的数据类型。
泛型如何调用?
//定义泛型方法
public static void Show2<T>(T a)
{
Console.WriteLine($"参数类型:{a.GetType().FullName},参数值:{a}");
}
// 调用,传递实参时,才给T指明具体类型。
Show2<int>(10);
泛型的约束:
为什么要使用泛型约束?好处是什么?
泛型的目的,相同的业务逻辑,支持不同的类型,如果一味的滥用,对代码安全性不利。
引入泛型约束主要控制泛型支持的类型在有限的范围内。
泛型约束的好处: 让代码异常出现在编译期间,而非运行期间,从而增强代码的安全性。
所谓的泛型约束,实际上就是约束的类型T。使T必须遵循一定的规则。比如T必须继承自某个类,或者T必须实现某个接口等等。
怎么给泛型指定约束?
只需要where关键字,加上约束的条件。如:where T: class 、 where T: struct
五种常用的泛型约束
where T :struct:约束类型参数必须是值类型,如整数、枚举等。
// where T : struct:要求类型参数必须是值类型,如整数、枚举等。
public class ValueTypeContainer<T> where T : struct
{
public T Value { get; set; }
public ValueTypeContainer(T value)
{
Value = value;
}
}
class Program
{
static void Main()
{
// 可以使用 int 这种值类型
ValueTypeContainer<int> intContainer = new ValueTypeContainer<int>(10);
// 下面这行代码会编译错误,因为 string 是引用类型
// ValueTypeContainer<string> stringContainer = new ValueTypeContainer<string>("test");
}
}0);
where T :class:约束类型参数必须是引用类型,像类、接口、委托等。
// where T :class 规定类型参数必须是引用类型,像类、接口、委托等。
public class ReferenceTypeContainer<T> where T : class
{
public T Value { get; set; }
public ReferenceTypeContainer(T value)
{
Value = value;
}
}
class MyClass { }
class Program
{
static void Main()
{
MyClass obj = new MyClass();
// 可以使用引用类型 MyClass
ReferenceTypeContainer<MyClass> refContainer = new ReferenceTypeContainer<MyClass>(obj);
// 下面这行代码会编译错误,因为 int 是值类型
// ReferenceTypeContainer<int> intContainer = new ReferenceTypeContainer<int>(10);
}
}
where T :new():约束要求类型参数必须有一个无参数的公共构造函数,这样就能在泛型类中使用new()创建实例。
//where T : new():要求类型参数必须有一个无参数的公共构造函数,
//这样就能在泛型类中使用 new T() 创建实例。
public class CreatableContainer<T> where T : new()
{
public T CreateInstance()
{
return new T();
}
}
class DefaultConstructibleClass
{
public DefaultConstructibleClass() { }
}
class Program
{
static void Main()
{
CreatableContainer<DefaultConstructibleClass> creator = new CreatableContainer<DefaultConstructibleClass>();
DefaultConstructibleClass instance = creator.CreateInstance();
}
}
where T :基类名:表示类型参数必须是指定基类或其派生类。
//where T : 基类名:表示类型参数必须是指定基类或其派生类。
public class Animal { }
public class Dog : Animal { }
public class AnimalProcessor<T> where T : Animal
{
public void Process(T animal)
{
// 处理动物的逻辑
}
}
class Program
{
static void Main()
{
Dog dog = new Dog();
AnimalProcessor<Dog> processor = new AnimalProcessor<Dog>();
processor.Process(dog);
// 可以使用基类 Animal 本身
Animal animal = new Animal();
AnimalProcessor<Animal> animalProcessor = new AnimalProcessor<Animal>();
animalProcessor.Process(animal);
}
}
where T :接口名:要求类型参数必须实现指定的接口。
//where T : 接口名:要求类型参数必须实现指定的接口。
public interface IMyInterface
{
void DoSomething();
}
public class ImplementingClass : IMyInterface
{
public void DoSomething()
{
System.Console.WriteLine("Doing something...");
}
}
public class InterfaceProcessor<T> where T : IMyInterface
{
public void CallMethod(T obj)
{
obj.DoSomething();
}
}
class Program
{
static void Main()
{
ImplementingClass impl = new ImplementingClass();
InterfaceProcessor<ImplementingClass> interfaceProcessor = new InterfaceProcessor<ImplementingClass>();
interfaceProcessor.CallMethod(impl);
}
}
泛型的协变和逆变:
协变和逆变概念:
协变(Covariance):协变允许使用比泛型类型参数指定的类型派生程度更大(更具体的子类型)的类型。使用out
关键字来控制返回值类型。
简单来说,就是可以将一个派生类的泛型对象隐式转换为基类的泛型对象。在 C# 中,协变只适用于引用类型,并且只能用于泛型接口和委托的输出位置(如返回类型)。
逆变(Contravariance):逆变允许使用比泛型类型参数指定的类型派生程度更小(更宽泛的基类型)的类型。使用in
关键字来控制参数的类型。
即可以将一个基类的泛型对象隐式转换为派生类的泛型对象。逆变同样只适用于引用类型,并且只能用于泛型接口和委托的输入位置(如方法参数)。
协变(Contravariance)示例:
using System;
// 定义一个协变的泛型接口
public interface IMyCovariantInterface<out T>
{
T GetValue();
}
// 实现协变接口的类
public class MyClass<T> : IMyCovariantInterface<T>
{
private T value;
public MyClass(T value)
{
this.value = value;
}
public T GetValue()
{
return value;
}
}
class Program
{
static void Main()
{
// 创建一个具体类型的对象
IMyCovariantInterface<string> stringInterface = new MyClass<string>("Hello");
// 进行协变转换
IMyCovariantInterface<object> objectInterface = stringInterface;
// 调用方法
object result = objectInterface.GetValue();
Console.WriteLine(result);
}
}
在上述代码中,
IMyCovariantInterface<out T>
是一个协变的泛型接口,使用out
关键字声明T
为协变类型参数。MyClass<T>
实现了该接口。在Main
方法中,我们创建了一个IMyCovariantInterface<string>
类型的对象,然后将其转换为IMyCovariantInterface<object>
类型,因为string
是object
的子类型,这种转换是安全的,这就是协变的体现。
逆变(Contravariance)示例:
using System;
// 定义一个逆变的泛型接口
public interface IMyContravariantInterface<in T>
{
void SetValue(T value);
}
// 实现逆变接口的类
public class MyAnotherClass<T> : IMyContravariantInterface<T>
{
private T value;
public void SetValue(T value)
{
this.value = value;
}
}
class Program
{
static void Main()
{
// 创建一个具体类型的对象
IMyContravariantInterface<object> objectInterface = new MyAnotherClass<object>();
// 进行逆变转换
IMyContravariantInterface<string> stringInterface = objectInterface;
// 调用方法
stringInterface.SetValue("World");
}
}
在这个例子中,
IMyContravariantInterface<in T>
是一个逆变的泛型接口,使用in
关键字声明T
为逆变类型参数。MyAnotherClass<T>
实现了该接口。在Main
方法中,我们创建了一个IMyContravariantInterface<object>
类型的对象,然后将其转换为IMyContravariantInterface<string>
类型,因为object
是string
的基类型,这种转换是安全的,这就是逆变的体现。
注意事项
只适用于引用类型:协变和逆变只适用于引用类型,不能用于值类型(如
int
、double
等)。类型参数使用位置限制:协变类型参数只能用于输出位置(如返回类型),逆变类型参数只能用于输入位置(如方法参数)。如果违反这个规则,编译器会报错。
支持范围:协变和逆变主要应用于泛型接口和委托,类和结构体不支持协变和逆变。