C# 中的泛型(Generics),这是一种允许程序员创建灵活、可重用的代码的功能。我们将了解泛型的基本概念、为什么要使用泛型、如何定义和使用泛型类和方法,以及泛型的一些高级特性。本课程将通过丰富的示例帮助您掌握泛型的使用,简单理解可以是一种模板。 
泛型的基本概念 泛型允许我们创建不指定具体数据类型的类、接口、方法和委托。通过使用泛型,我们可以编写更加灵活和可重用的代码。 
为什么使用泛型?   类型安全 性能 :使用泛型可以避免装箱(boxing)和拆箱(unboxing)操作,提升性能。 代码重用 示例:不使用泛型的类   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();         }     } } 
在上面的例子中, Box  类使用  object  类型来存储内容,这意味着它可以接受任何类型的数据。然而,这会导致装箱和拆箱操作,以及潜在的运行时类型错误。 
示例:使用泛型的类   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  类型,这样就避免了装箱和拆箱操作,并且保证了类型安全。 
定义和使用泛型方法 泛型也可以用于方法。泛型方法可以在非泛型类中定义,也可以在泛型类中定义。 
示例:泛型方法   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();         }     } } 
在这个例子中,我们定义了一个泛型方法  Max ,它接受两个参数并返回较大的一个。我们使用了泛型约束  where T : IComparable<T> ,这表示  T  必须实现  IComparable<T>  接口,这样我们就可以调用  CompareTo  方法。 
泛型约束 泛型约束用于指定泛型类型参数必须满足的条件。常见的泛型约束包括: 
where T : struct where T : class where T : new() where T : baseClass where T : interface 示例:带约束的泛型类   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();         }     } } 
在这个例子中,我们定义了一个泛型类  NullableValue<T> ,它使用了约束  where T : struct ,这意味着  T  必须是一个值类型。这个类允许我们创建一个可以为 null 的值类型的封装,类似于 .NET 中的  Nullable<T> 。 
泛型接口和泛型委托 泛型也可以用于接口和委托的定义,使它们能够以类型安全的方式处理多种数据类型。 
示例:泛型接口   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  类型的操作。 
示例:泛型委托   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();         }     } } 
在这个例子中,我们定义了一个泛型委托  Transformer<T> ,它可以引用任何接受一个  T  类型参数并返回  T  类型结果的方法。然后我们创建了一个  squareTransformer  委托实例,并将其关联到  MathOperations.Square  方法。 
泛型在集合中的应用 泛型在 .NET 集合类中广泛使用,例如  List<T> 、 Dictionary<TKey, TValue> 、 HashSet<T>  等。 
示例:泛型集合   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();         }     } } 在这些例子中,我们创建了几种不同类型的泛型集合,并对它们进行了操作。泛型集合提供了类型安全的数据存储和访问,并且可以减少需要进行的类型转换,从而提高性能。 
泛型方法 泛型也可以用于方法,允许在方法级别定义类型参数。这使得我们可以编写能够处理不同类型数据的灵活方法。 
示例:泛型方法   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();         }     } } 
泛型约束的组合 泛型约束可以组合使用,以满足更复杂的要求。 
示例:组合泛型约束   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)允许方法接受比声明的参数类型更通用的类型。 示例:泛型协变   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();         }     } } 
在这个例子中, IProducer<out T>  接口声明了一个协变的泛型参数  T (使用  out  关键字)。 重点在于生产者的变化。 
示例:泛型逆变   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>  类型的变量,因为  Apple  是  Gala  的基类。 
泛型实际应用示例 通用缓存管理器   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();         }     } } 
通用工厂模式   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();         }     } } 
总结 泛型是 C# 中非常强大的特性,它为开发者提供了编写灵活、可重用和类型安全代码的能力。通过使用泛型约束,开发者可以确保泛型类型和方法的正确性和安全性。泛型的协变和逆变进一步增加了泛型类型的灵活性,使得代码更加通用和适应不同的场景。 
阅读原文:原文链接