.NET 泛型编程(泛型类、泛型方法、泛型接口、泛型委托、泛型约束)
文章目录
- 前言
- 什么是泛型编程?
- 泛型编程优点
- 泛型类
- 泛型方法
- 泛型接口
- 泛型委托
- 泛型约束
- 泛型约束的使用场景
- 类约束
- 接口约束
- 构造函数约束
- 值类型约束
- 引用类型约束
 
前言
转载: 方程式sunny
视频教程: 跟着sunny老师学C#
源码: gitee仓库
什么是泛型编程?
- 泛型编程(Generic Programming)是一种编程范式,核心思想是将数据类型参数化,使代码能够在不针对特定类型的情况下编写,从而实现 “一套逻辑,多类型适配”。
- 通俗来说,就是编写可以重复使用的代码,以适用多种类型的数据。
- 假设你写了一个函数来处理整数,然后又写了一个几乎一模一样的函数来处理字符串,这样的代码既冗余又难以维护,而泛型编程就能很好的解决这个问题,你只需编写一次代码,就可以让它适用于多种不同的数据类型。
泛型编程优点
- 减少重复代码:不用为每种数据类型编写一套代码,减少了代码量,也降低了出错的风险。
- 提高代码的通用性:一段泛型代码可以处理多种类型的数据,让代码更具通用性。
- 增强代码的安全性:使用泛型时,编译器可以检查类型是否正确,这样可以避免很多潜在的错误。
- 优化性能:泛型编程在编译时会生成针对具体类型的代码,没有装箱和拆箱的过程,不会影响运行时的性能。
泛型类
泛型类是在类名后面加上尖括号 <T>,其中 T 是类型参数,可以是任何符号或字母,代表类可以操作的某种类型。
// 实例化泛型类并使用
var intInstance = new GenericClass<int>();//实例化泛型类用<>括号携带类型参数int
intInstance.SetValue(42);
Console.WriteLine(intInstance.GetValue());  // 输出: 42
var stringInstance = new GenericClass<string>();//实例化泛型类用<>括号携带类型参数string
stringInstance.SetValue("Hello, World!");
Console.WriteLine(stringInstance.GetValue());  // 输出: Hello, World!
// 定义泛型类
public class GenericClass<T>
{private T _value;
public void SetValue(T value){_value = value;}
public T GetValue(){return _value;}
}
泛型方法
- 泛型方法是在方法名之前加上尖括号 <T>,其中T是类型参数,表示方法可以操作的某种类型。
- 注意:并不是只有泛型类中可以定义泛型方法,普通类中也可以定义泛型方法。
// 使用泛型方法
int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"x: {x}, y: {y}");  // 输出: x: 2, y: 1
string str1 = "first", str2 = "second";
Swap(ref str1, ref str2);
Console.WriteLine($"str1: {str1}, str2: {str2}");  // 输出: str1: second, str2: first
// 定义一个泛型方法
static void Swap<T>(ref T a, ref T b)
{T temp = a;a = b;b = temp;
}
泛型接口
- 定义泛型接口的语法与泛型类类似。接口名后面的尖括号 <T>表示这是一个泛型接口,T是一个占位符,表示数据的类型。
- 下面的例子定义了一个通用的存储接口 IRepository<T>,可以处理任意类型的T,如int、string,或者自定义的对象。
// 定义泛型接口
public interface IRepository<T>
{void Add(T item);T Get(int id);void Delete(T item);
}
// 实现泛型接口,指定为 int 类型
public class IntRepository : IRepository<int>
{private List<int> _items = new List<int>();
public void Add(int item){_items.Add(item);}
public int Get(int id){return _items[id];}
public void Delete(int item){_items.Remove(item);}
}
// 实现泛型接口,指定为 string 类型
public class StringRepository : IRepository<string>
{private List<string> _items = new List<string>();
public void Add(string item){_items.Add(item);}
public string Get(int id){return _items[id];}
public void Delete(string item){_items.Remove(item);}
}
// 使用泛型接口
class Program
{static void Main(string[] args){IRepository<int> intRepo = new IntRepository();intRepo.Add(10);Console.WriteLine(intRepo.Get(0));  // 输出: 10
IRepository<string> stringRepo = new StringRepository();stringRepo.Add("Hello");Console.WriteLine(stringRepo.Get(0));  // 输出: Hello}
}
泛型委托
- 定义泛型委托的语法与泛型类和泛型接口类似。委托名后面的尖括号 <T>表示这是一个泛型委托,T是一个占位符,表示数据的类型。
- 下面的例子定义了一个通用的委托 MyDelegate<T>,可以处理任意类型的T,如int、string,或者自定义的对象。
class Program
{// 定义泛型委托public delegate void MyDelegate<T>(T item);
static void Main(string[] args){// 使用泛型委托,传递类型参数intMyDelegate<int> intDelegate = (int item) =>{Console.WriteLine($"Int: {item}");};
// 调用委托intDelegate(42);  // 输出: Int: 42}
}
- 泛型委托不仅可以用于单个类型,还可以处理多个类型参数,
- 例如处理多个输入参数或返回值的委托,以下是定义具有两个泛型参数的委托的示例,MyFunc<T1, T2, TResult>是一个带有两个输入类型和一个返回类型的泛型委托。
- 通过这种方式,我们可以处理更加复杂的场景,例如根据多个输入生成一个返回值。
// 使用MyFunc<T1, T2, TResult>
MyFunc<int, string, string> func = Combine;//这里T1是int,T2是string,TResult是string
string result = func(100, "Hello");
Console.WriteLine(result);  // 输出: 100 - Hello
// 定义 Combine 方法
string Combine(int number, string text)
{return $"{number} - {text}";
}
// 定义一个带有两个泛型参数的委托
public delegate TResult MyFunc<T1, T2, TResult>(T1 item1, T2 item2);
泛型委托的使用场景
// 1. Action<T> 示例:不返回值的泛型委托,接受一个参数
Action<string> printMessage = (string message) =>
{Console.WriteLine($"Message: {message}");
};
printMessage("Hello, World!");  // 输出: Message: Hello, World!
// 2. Func<T, TResult> 示例:返回值的泛型委托,接受多个参数并返回一个结果
Func<int, int, int> addNumbers = (int x, int y) =>
{return x + y;
};
int sum = addNumbers(10, 20);
Console.WriteLine($"Sum: {sum}");  // 输出: Sum: 30
// 3. Predicate<T> 示例:返回布尔值的泛型委托,用于判断条件
Predicate<int> isEven = (int number) =>
{return number % 2 == 0;
};
bool result = isEven(10);
Console.WriteLine($"Is 10 even? {result}");  // 输出: Is 10 even? True
// 结合 Predicate 和 List.FindAll 方法,筛选出偶数
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> evenNumbers = numbers.FindAll(isEven);
Console.WriteLine("Even numbers:");
foreach (int number in evenNumbers)
{Console.WriteLine(number);  // 输出: 2, 4, 6, 8, 10
}
泛型约束
- 定义泛型约束的语法与定义泛型基本相同,只需要在类型参数<T>后面通过where关键字来指定约束条件。
- 常见的约束类型包括继承约束、接口约束、构造函数约束等。
- 下面的例子定义了一个使用泛型约束的类 Repository<T>,其中T必须继承自Entity,并且必须有一个无参构造函数,如果不满足这两个条件编译器就会报错 。
// 定义基类 Entity
public class Entity
{public int Id { get; set; }
}
// 定义泛型类 Repository<T>,带有两个约束:T 必须继承自 Entity 并且有无参构造函数
public class Repository<T> where T : Entity, new()
{public T Create(){return new T();  // 这里要求 T 必须有无参构造函数}
}
// 定义符合约束的类 ValidEntity,有无参构造函数
public class ValidEntity : Entity
{public ValidEntity(){Id = 1;}
}
// 定义不符合约束的类 InvalidEntity,没有无参构造函数
public class InvalidEntity : Entity
{public InvalidEntity(int id){Id = id;}
}
class Program
{static void Main(string[] args){// 使用符合约束的类 ValidEntityRepository<ValidEntity> validRepo = new Repository<ValidEntity>();ValidEntity validEntity = validRepo.Create();Console.WriteLine($"ValidEntity ID: {validEntity.Id}");  // 输出: ValidEntity ID: 1
// 使用不符合约束的类 InvalidEntity 会导致编译错误// Repository<InvalidEntity> invalidRepo = new Repository<InvalidEntity>();// InvalidEntity invalidEntity = invalidRepo.Create();  // 无法编译,InvalidEntity 缺少无参构造函数
// 编译错误提示:// "InvalidEntity" 没有无参构造函数,无法满足 new() 约束。}
}
泛型约束的使用场景
泛型约束可以确保类型的安全性,减少不必要的类型转换错误。以下是一些使用泛型约束的场景:下面有具体的实例代码
| 约束类型 | 描述 | 使用场景 | 
|---|---|---|
| where T : class | 限制类型参数为引用类型。 | 适用于泛型类必须处理引用类型的场景,如对象操作。 | 
| where T : struct | 限制类型参数为值类型。 | 适用于泛型类处理数值类型时,确保性能和类型安全。 | 
| where T : new() | 限制类型参数必须具有无参构造函数。 | 适用于需要实例化泛型类型的场景。 | 
| where T : BaseClass | 限制类型参数必须是某个类的子类。 | 适用于泛型类型继承某个基类,复用基类功能的场景。 | 
| where T : InterfaceName | 限制类型参数必须实现某个接口。 | 适用于泛型类型必须实现接口,并使用接口方法的场景。 | 
类约束
限制类型参数必须是某个类或某个类的子类。适用于当你希望类型参数继承某个类并复用该类的功能时。
// 定义一个基类
public class Animal
{public string Name { get; set; }public void Speak(){Console.WriteLine($"{Name} makes a sound.");}
}
// 定义一个带有类约束的泛型类,T 必须继承自 Animal
public class AnimalHouse<T> where T : Animal
{public void Welcome(T animal){animal.Speak();  // 可以调用基类 Animal 的方法}
}
class Program
{static void Main(){AnimalHouse<Animal> house = new AnimalHouse<Animal>();Animal dog = new Animal { Name = "Dog" };house.Welcome(dog);  // 输出: Dog makes a sound.}
}
接口约束
限制类型参数必须实现某个接口。适用于当你希望类型参数实现某些方法或属性时。
// 定义一个接口
public interface IRun
{void Run();
}
// 实现接口的类
public class Person : IRun
{public void Run(){Console.WriteLine("Person is running");}
}
// 定义一个带有接口约束的泛型类,T 必须实现 IRun 接口
public class Race<T> where T : IRun
{public void Start(T runner){runner.Run();  // 可以调用 IRun 接口中的方法}
}
class Program
{static void Main(){Race<Person> race = new Race<Person>();Person person = new Person();race.Start(person);  // 输出: Person is running}
}
构造函数约束
限制类型参数必须有一个无参构造函数。这常用于当你需要在泛型类或方法中创建类型实例时。
// 定义一个带有构造函数约束的泛型类,T 必须有无参构造函数
public class Factory<T> where T : new()
{public T CreateInstance(){return new T();  // 可以安全地创建 T 类型的实例}
}
// 使用该类
class Program
{static void Main(){Factory<Person> factory = new Factory<Person>();Person person = factory.CreateInstance();Console.WriteLine("Instance of Person created.");}
}
// Person 类有一个无参构造函数
public class Person
{public string Name { get; set; }
}
值类型约束
限制类型参数必须是值类型。这在处理数值或结构体时非常有用,因为值类型有更好的性能表现。
// 定义一个带有值类型约束的泛型类,T 必须是值类型
public class MathOperations<T> where T : struct
{public T Add(T a, T b){dynamic x = a;  // 使用动态类型进行数学操作dynamic y = b;return x + y;}
}
// 使用该类
class Program
{static void Main(){MathOperations<int> mathOps = new MathOperations<int>();int result = mathOps.Add(10, 20);Console.WriteLine($"Result: {result}");  // 输出: Result: 30}
}
引用类型约束
限制类型参数必须是引用类型。这在你希望处理对象而非值类型时非常有用。
// 定义一个带有引用类型约束的泛型类,T 必须是引用类型
public class Repository<T> where T : class
{private List<T> _items = new List<T>();
public void AddItem(T item){_items.Add(item);}
public T GetItem(int index){return _items[index];}
}
// 使用该类
class Program
{static void Main(){Repository<Person> repo = new Repository<Person>();repo.AddItem(new Person { Name = "John" });Person person = repo.GetItem(0);Console.WriteLine($"Retrieved Person: {person.Name}");  // 输出: Retrieved Person: John}
}
public class Person
{public string Name { get; set; }
}
