编辑
2025-09-23
C#
00

目录

泛型的基本概念
为什么使用泛型?
示例:不使用泛型的类
示例:使用泛型的类
定义和使用泛型方法
示例:泛型方法
泛型约束
示例:带约束的泛型类
泛型接口和泛型委托
示例:泛型接口
示例:泛型委托
泛型在集合中的应用
示例:泛型集合
泛型方法
示例:泛型方法
泛型约束的组合
示例:组合泛型约束
泛型的协变和逆变
示例:泛型协变
示例:泛型逆变
泛型实际应用示例
通用缓存管理器
通用工厂模式
总结

C# 中的泛型(Generics),这是一种允许程序员创建灵活、可重用的代码的功能。我们将了解泛型的基本概念、为什么要使用泛型、如何定义和使用泛型类和方法,以及泛型的一些高级特性。本课程将通过丰富的示例帮助您掌握泛型的使用,简单理解可以是一种模板。

泛型的基本概念

泛型允许我们创建不指定具体数据类型的类、接口、方法和委托。通过使用泛型,我们可以编写更加灵活和可重用的代码。

为什么使用泛型?

  • 类型安全:泛型提供了编译时类型检查,减少了运行时错误。
  • 性能:使用泛型可以避免装箱(boxing)和拆箱(unboxing)操作,提升性能。
  • 代码重用:可以使用同一个类或方法处理不同的数据类型。

示例:不使用泛型的类

C#
namespace AppGenerics { public class Box { private object content; public void SetContent(object content) { this.content = content; } public object GetContent() { return content; } } internal class Program { static void Main(string[] args) { Box box = new Box(); box.SetContent(42); // 装箱操作 int number = (int)box.GetContent(); // 需要显式拆箱 Console.WriteLine(number); Console.ReadKey(); } } }

image.png 在上面的例子中,Box 类使用 object 类型来存储内容,这意味着它可以接受任何类型的数据。然而,这会导致装箱和拆箱操作,以及潜在的运行时类型错误。

示例:使用泛型的类

C#
namespace AppGenerics { public class Box<T> { private T content; public void SetContent(T content) { this.content = content; } public T GetContent() { return content; } } internal class Program { static void Main(string[] args) { Box<int> intBox = new Box<int>(); intBox.SetContent(42); // 无需装箱操作 int number = intBox.GetContent(); // 无需拆箱,类型安全 Console.WriteLine(number); Console.ReadKey(); } } }

在这个例子中,我们定义了一个泛型类 Box<T>,其中 T 是一个占位符,代表将来会被实际的数据类型替换。当我们创建 Box<int> 实例时,T 被替换为 int 类型,这样就避免了装箱和拆箱操作,并且保证了类型安全。

定义和使用泛型方法

泛型也可以用于方法。泛型方法可以在非泛型类中定义,也可以在泛型类中定义。

示例:泛型方法

C#
namespace AppGenerics { public class Utils { public static T Max<T>(T a, T b) where T : IComparable<T> { return a.CompareTo(b) > 0 ? a : b; } } internal class Program { static void Main(string[] args) { int maxInt = Utils.Max(10, 20); // 调用泛型方法 string maxString = Utils.Max("apple", "orange"); // 调用泛型方法 Console.WriteLine("Max int: " + maxInt); Console.ReadKey(); } } }

image.png

在这个例子中,我们定义了一个泛型方法 Max,它接受两个参数并返回较大的一个。我们使用了泛型约束 where T : IComparable<T>,这表示 T 必须实现 IComparable<T> 接口,这样我们就可以调用 CompareTo 方法。

泛型约束

泛型约束用于指定泛型类型参数必须满足的条件。常见的泛型约束包括:

  • where T : structT 必须是值类型。
  • where T : classT 必须是引用类型。
  • where T : new()T 必须有一个无参数的构造函数。
  • where T : baseClassT 必须是指定基类或其派生类。
  • where T : interfaceT 必须实现指定的接口。

示例:带约束的泛型类

C#
namespace AppGenerics { public class NullableValue<T> where T : struct { private T? value; public NullableValue(T? value) { this.value = value; } public bool HasValue => value.HasValue; public T GetValueOrDefault() => value.GetValueOrDefault(); } internal class Program { static void Main(string[] args) { NullableValue<int> nullableInt = new NullableValue<int>(null); bool hasValue = nullableInt.HasValue; // false int defaultValue = nullableInt.GetValueOrDefault(); // 0 Console.WriteLine($"HasValue: {hasValue}, DefaultValue: {defaultValue}"); Console.ReadKey(); } } }

image.png

在这个例子中,我们定义了一个泛型类 NullableValue<T>,它使用了约束 where T : struct,这意味着 T 必须是一个值类型。这个类允许我们创建一个可以为 null 的值类型的封装,类似于 .NET 中的 Nullable<T>

泛型接口和泛型委托

泛型也可以用于接口和委托的定义,使它们能够以类型安全的方式处理多种数据类型。

示例:泛型接口

C#
public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } } public interface IRepository<T> { void Add(T item); T GetById(int id); IEnumerable<T> GetAll(); } public class ProductRepository : IRepository<Product> { public void Add(Product item) { } public Product GetById(int id) { return new Product(); } public IEnumerable<Product> GetAll() { return new List<Product>(); } }

在这个例子中,我们定义了一个泛型接口 IRepository<T>,它提供了对任何类型 T 的通用存储库操作。然后我们实现了一个具体的 ProductRepository 类,它实现了 IRepository<Product> 接口,提供了对 Product 类型的操作。

示例:泛型委托

C#
namespace AppGenerics { public delegate T Transformer<T>(T input); public class MathOperations { public static int Square(int number) => number * number; } internal class Program { static void Main(string[] args) { Transformer<int> squareTransformer = MathOperations.Square; int result = squareTransformer(5); // 结果为 25 Console.WriteLine(result); Console.ReadKey(); } } }

image.png

在这个例子中,我们定义了一个泛型委托 Transformer<T>,它可以引用任何接受一个 T 类型参数并返回 T 类型结果的方法。然后我们创建了一个 squareTransformer 委托实例,并将其关联到 MathOperations.Square 方法。

泛型在集合中的应用

泛型在 .NET 集合类中广泛使用,例如 List<T>Dictionary<TKey, TValue>HashSet<T> 等。

示例:泛型集合

C#
namespace AppGenerics { internal class Program { static void Main(string[] args) { List<string> names = new List<string>(); names.Add("Alice"); names.Add("Bob"); Dictionary<int, string> userDictionary = new Dictionary<int, string>(); userDictionary.Add(1, "Alice"); userDictionary.Add(2, "Bob"); HashSet<int> uniqueNumbers = new HashSet<int>(); uniqueNumbers.Add(1); uniqueNumbers.Add(2); uniqueNumbers.Add(1); // 不会被加入集合 Console.ReadKey(); } } }

在这些例子中,我们创建了几种不同类型的泛型集合,并对它们进行了操作。泛型集合提供了类型安全的数据存储和访问,并且可以减少需要进行的类型转换,从而提高性能。

泛型方法

泛型也可以用于方法,允许在方法级别定义类型参数。这使得我们可以编写能够处理不同类型数据的灵活方法。

示例:泛型方法

C#
namespace AppGenerics { public static class GenericUtilities { public static T Max<T>(T a, T b) where T : IComparable<T> { return a.CompareTo(b) > 0 ? a : b; } public static T Min<T>(T a, T b) where T : IComparable<T> { return a.CompareTo(b) < 0 ? a : b; } public static T Max<T>(params T[] values) where T : IComparable<T> { if (values == null || values.Length == 0) throw new ArgumentException("必须提供至少一个值"); T max = values[0]; for (int i = 1; i < values.Length; i++) { if (values[i].CompareTo(max) > 0) max = values[i]; } return max; } } internal class Program { static void Main(string[] args) { int maxNumber = GenericUtilities.Max(5, 10, 3, 8, 15); string longest = GenericUtilities.Max("apple", "banana", "kiwi", "strawberry"); Console.WriteLine($"最大值:{maxNumber}"); Console.WriteLine($"最长的字符串:{longest}"); Console.ReadKey(); } } }

image.png

泛型约束的组合

泛型约束可以组合使用,以满足更复杂的要求。

示例:组合泛型约束

C#
namespace AppGenerics { public class Factory<T> where T : class, new() { public T CreateInstance() { return new T(); } } public class MyClass { public MyClass() { /* 构造函数 */ } } internal class Program { static void Main(string[] args) { Factory<MyClass> factory = new Factory<MyClass>(); MyClass myClassInstance = factory.CreateInstance(); Console.ReadKey(); } } }

在这个例子中,我们定义了一个泛型类 Factory<T>,它使用了组合约束 where T : class, new(),这意味着 T 必须是一个引用类型,并且必须有一个无参数的构造函数。这样,Factory<T> 类的 CreateInstance 方法就可以创建 T 类型的新实例。

泛型的协变和逆变

在 C# 中,泛型接口和泛型委托可以指定协变和逆变。

  • 协变(Covariance)允许方法返回比声明的返回类型更具体的类型。
  • 逆变(Contravariance)允许方法接受比声明的参数类型更通用的类型。

示例:泛型协变

C#
namespace AppGenerics { // 定义协变接口 - 注意"out"关键字 public interface IProducer<out T> { T Produce(); // T只用于返回值位置 } // 定义水果层次结构 public class Fruit { } public class Apple : Fruit { } public class Gala : Apple { } // 实现基本的生产者类 public class FruitProducer : IProducer<Fruit> { public Fruit Produce() => new Fruit(); } public class AppleProducer : IProducer<Apple> { public Apple Produce() => new Apple(); } public class GalaProducer : IProducer<Gala> { public Gala Produce() => new Gala(); } internal class Program { static void Main(string[] args) { // 1. 最基本的协变示例 IProducer<Apple> appleProducer = new AppleProducer(); IProducer<Fruit> fruitProducer = appleProducer; // 可以将Apple生产者视为Fruit生产者 // 2. 更复杂的示例 IProducer<Gala> galaProducer1 = new GalaProducer(); IProducer<Apple> appleProducer2 = galaProducer1; // Gala生产者可以视为Apple生产者 IProducer<Fruit> fruitProducer2 = galaProducer1; // Gala生产者也可以视为Fruit生产者 Fruit f1 = fruitProducer.Produce(); Fruit f2 = appleProducer2.Produce(); Fruit f3 = fruitProducer2.Produce(); Console.WriteLine($"fruitProducer 实际产生: {f1.GetType().Name}"); Console.WriteLine($"appleProducer2 实际产生: {f2.GetType().Name}"); Console.WriteLine($"fruitProducer2 实际产生: {f3.GetType().Name}"); Console.ReadKey(); } } }

image.png

在这个例子中,IProducer<out T> 接口声明了一个协变的泛型参数 T(使用 out 关键字)。 重点在于生产者的变化。

示例:泛型逆变

C#
namespace AppGenerics { public interface IConsumer<in T> { void Consume(T item); } public class Apple { } public class Gala : Apple { } public class AppleConsumer : IConsumer<Apple> { public void Consume(Apple item) { Console.WriteLine("正在消费苹果..."); } } internal class Program { static void Main(string[] args) { IConsumer<Apple> appleConsumer = new AppleConsumer(); IConsumer<Gala> galaConsumer = appleConsumer; // 逆变允许这样做 galaConsumer.Consume(new Gala()); appleConsumer.Consume(new Apple()); Console.ReadKey(); } } }

在这个例子中,IConsumer<in T> 接口声明了一个逆变的泛型参数 T(使用 in 关键字)。这意味着我们可以将 IConsumer<Apple> 类型的对象赋值给 IConsumer<Gala> 类型的变量,因为 AppleGala 的基类。

泛型实际应用示例

通用缓存管理器

C#
namespace AppGenerics { public class CacheManager<T> { private readonly Dictionary<string, (T Value, DateTime ExpiryTime)> _cache = new(); private readonly TimeSpan _defaultExpiry = TimeSpan.FromMinutes(30); public void Set(string key, T value, TimeSpan? expiryTime = null) { var expiry = DateTime.Now + (expiryTime ?? _defaultExpiry); _cache[key] = (value, expiry); } public bool TryGet(string key, out T value) { value = default; if (!_cache.TryGetValue(key, out var cacheItem)) return false; if (DateTime.Now > cacheItem.ExpiryTime) { _cache.Remove(key); return false; } value = cacheItem.Value; return true; } } // 使用示例 public class UserProfile { public int Id { get; set; } public string Name { get; set; } } internal class Program { static void Main(string[] args) { // 创建缓存管理器实例 var userCache = new CacheManager<UserProfile>(); // 缓存用户信息 userCache.Set("user:123", new UserProfile { Id = 123, Name = "John" }); // 获取缓存的用户信息 if (userCache.TryGet("user:123", out var user)) { Console.WriteLine($"Found user: {user.Name}"); } Console.ReadKey(); } } }

image.png

通用工厂模式

C#
namespace AppGenerics { public interface IFactory<T> { T Create(); } public class ConfigurableFactory<T> : IFactory<T> where T : class { private readonly Func<T> _creator; public ConfigurableFactory(Func<T> creator) { _creator = creator; } public T Create() { return _creator(); } } // 使用示例 public interface ILogger { void Log(string message); } public class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine($"[{DateTime.Now}] {message}"); } } public class FileLogger : ILogger { private readonly string _path; public FileLogger(string path) { _path = path; } public void Log(string message) { File.AppendAllText(_path, $"[{DateTime.Now}] {message}\n"); } } internal class Program { static void Main(string[] args) { // 创建工厂实例 var consoleLoggerFactory = new ConfigurableFactory<ILogger>(() => new ConsoleLogger()); var fileLoggerFactory = new ConfigurableFactory<ILogger>(() => new FileLogger("app.log")); // 使用工厂创建实例 ILogger logger1 = consoleLoggerFactory.Create(); ILogger logger2 = fileLoggerFactory.Create(); logger1.Log("Hello, World!"); logger2.Log("Hello, World!"); Console.ReadKey(); } } }

image.png

总结

泛型是 C# 中非常强大的特性,它为开发者提供了编写灵活、可重用和类型安全代码的能力。通过使用泛型约束,开发者可以确保泛型类型和方法的正确性和安全性。泛型的协变和逆变进一步增加了泛型类型的灵活性,使得代码更加通用和适应不同的场景。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!