文章目录
前言
转载: 方程式sunny
视频教程: 跟着sunny老师学C#
源码: gitee仓库
什么是泛型编程?
- 泛型编程(Generic Programming)是一种编程范式,核心思想是将数据类型参数化,使代码能够在不针对特定类型的情况下编写,从而实现 "一套逻辑,多类型适配"。
- 通俗来说,就是编写可以重复使用的代码,以适用多种类型的数据。
- 假设你写了一个函数来处理整数,然后又写了一个几乎一模一样的函数来处理字符串,这样的代码既冗余又难以维护,而泛型编程就能很好的解决这个问题,你只需编写一次代码,就可以让它适用于多种不同的数据类型。
泛型编程优点
- 减少重复代码:不用为每种数据类型编写一套代码,减少了代码量,也降低了出错的风险。
- 提高代码的通用性:一段泛型代码可以处理多种类型的数据,让代码更具通用性。
- 增强代码的安全性:使用泛型时,编译器可以检查类型是否正确,这样可以避免很多潜在的错误。
- 优化性能:泛型编程在编译时会生成针对具体类型的代码,没有装箱和拆箱的过程,不会影响运行时的性能。
泛型类
泛型类是在类名后面加上尖括号 <T>,其中 T 是类型参数,可以是任何符号或字母,代表类可以操作的某种类型。
csharp
// 实例化泛型类并使用
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是类型参数,表示方法可以操作的某种类型。 - 注意:并不是只有泛型类中可以定义泛型方法,普通类中也可以定义泛型方法。
csharp
// 使用泛型方法
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,或者自定义的对象。
csharp
// 定义泛型接口
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,或者自定义的对象。
csharp
class Program
{
// 定义泛型委托
public delegate void MyDelegate<T>(T item);
static void Main(string[] args)
{
// 使用泛型委托,传递类型参数int
MyDelegate<int> intDelegate = (int item) =>
{
Console.WriteLine($"Int: {item}");
};
// 调用委托
intDelegate(42); // 输出: Int: 42
}
}
- 泛型委托不仅可以用于单个类型,还可以处理多个类型参数,
- 例如处理多个输入参数或返回值的委托,以下是定义具有两个泛型参数的委托的示例,
MyFunc<T1, T2, TResult>是一个带有两个输入类型和一个返回类型的泛型委托。 - 通过这种方式,我们可以处理更加复杂的场景,例如根据多个输入生成一个返回值。
csharp
// 使用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);
泛型委托的使用场景
csharp
// 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,并且必须有一个无参构造函数,如果不满足这两个条件编译器就会报错 。
csharp
// 定义基类 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)
{
// 使用符合约束的类 ValidEntity
Repository<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 |
限制类型参数必须实现某个接口。 | 适用于泛型类型必须实现接口,并使用接口方法的场景。 |
类约束
限制类型参数必须是某个类或某个类的子类。适用于当你希望类型参数继承某个类并复用该类的功能时。
csharp
// 定义一个基类
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.
}
}
接口约束
限制类型参数必须实现某个接口。适用于当你希望类型参数实现某些方法或属性时。
csharp
// 定义一个接口
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
}
}
构造函数约束
限制类型参数必须有一个无参构造函数。这常用于当你需要在泛型类或方法中创建类型实例时。
csharp
// 定义一个带有构造函数约束的泛型类,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; }
}
值类型约束
限制类型参数必须是值类型。这在处理数值或结构体时非常有用,因为值类型有更好的性能表现。
csharp
// 定义一个带有值类型约束的泛型类,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
}
}
引用类型约束
限制类型参数必须是引用类型。这在你希望处理对象而非值类型时非常有用。
csharp
// 定义一个带有引用类型约束的泛型类,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; }
}