C# 中的泛型(Generics),这是一种允许程序员创建灵活、可重用的代码的功能。我们将了解泛型的基本概念、为什么要使用泛型、如何定义和使用泛型类和方法,以及泛型的一些高级特性。本课程将通过丰富的示例帮助您掌握泛型的使用,简单理解可以是一种模板。
泛型允许我们创建不指定具体数据类型的类、接口、方法和委托。通过使用泛型,我们可以编写更加灵活和可重用的代码。
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();
}
}
}
在上面的例子中,
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();
}
}
}
在这个例子中,我们定义了一个泛型方法 Max
,它接受两个参数并返回较大的一个。我们使用了泛型约束 where T : IComparable<T>
,这表示 T
必须实现 IComparable<T>
接口,这样我们就可以调用 CompareTo
方法。
泛型约束用于指定泛型类型参数必须满足的条件。常见的泛型约束包括:
where T : struct
:T
必须是值类型。where T : class
:T
必须是引用类型。where T : new()
:T
必须有一个无参数的构造函数。where T : baseClass
:T
必须是指定基类或其派生类。where T : interface
:T
必须实现指定的接口。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();
}
}
}
在这个例子中,我们定义了一个泛型类 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();
}
}
}
在这个例子中,我们定义了一个泛型委托 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();
}
}
}
泛型约束可以组合使用,以满足更复杂的要求。
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# 中,泛型接口和泛型委托可以指定协变和逆变。
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();
}
}
}
在这个例子中,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>
类型的变量,因为 Apple
是 Gala
的基类。
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();
}
}
}
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();
}
}
}
泛型是 C# 中非常强大的特性,它为开发者提供了编写灵活、可重用和类型安全代码的能力。通过使用泛型约束,开发者可以确保泛型类型和方法的正确性和安全性。泛型的协变和逆变进一步增加了泛型类型的灵活性,使得代码更加通用和适应不同的场景。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!