.NET 泛型编程(泛型类、泛型方法、泛型接口、泛型委托、泛型约束)

文章目录


前言

转载: 方程式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,如 intstring,或者自定义的对象。
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,如 intstring,或者自定义的对象。
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; }
}

相关推荐
Tiger_shl2 小时前
SqlConnection、SqlCommand 和 SqlDataAdapter
开发语言·数据库·c#
yi碗汤园4 小时前
Visual Studio常用的快捷键
开发语言·ide·c#·编辑器·visual studio
MM_MS6 小时前
C#小案例-->汽车租聘系统计价功能
c#·汽车·简单工厂模式·抽象工厂模式·visual studio
MM_MS6 小时前
WinForm+C#小案例--->爱心跑马灯演示
开发语言·c#·visual studio
福尔摩斯张6 小时前
C语言核心:string函数族处理与递归实战
c语言·开发语言·数据结构·c++·算法·c#
qq_353199258 小时前
鼠标滑动或横拉用户控件无闪缩
c#
七七墨染20 小时前
DotMemory系列:5. 如何实现自动化抓取和应用自托管
运维·c#·自动化
王家羽翼-王羽21 小时前
C# 连接 PLC 的S7西门子协议读写,样例分享
c#
斯内科21 小时前
C#进行CAN【控制器局域网】通讯
c#·can·pcanbasic.net
张人玉1 天前
C#WPF——MVVM框架编写管理系统所遇到的问题
开发语言·c#·wpf·mvvm框架