C# 泛型

泛型(Generics)是 .NET 核心特性之一,它允许开发者在定义类、方法、接口或委托时使用类型占位符,从而在不牺牲类型安全的前提下提高代码的重用性和性能。以下将从基础概念到高级应用,系统性地介绍 .NET Core 中的泛型机制。

1. 泛型方法 (Generic Methods)

泛型方法允许在方法签名中定义类型参数,使得同一个方法可以处理多种数据类型,而无需为每种类型编写重载版本。

cs 复制代码
public class GenericHelper
{
    // T 是类型参数,由调用者决定
    public static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }

    public static T GetDefault<T>()
    {
        return default(T);
    }
}

// 使用示例
int x = 10, y = 20;
GenericHelper.Swap<int>(ref x, ref y); // 显式指定类型
GenericHelper.Swap(ref x, ref y);      // 编译器自动推断类型
  • ‌类型推断 ‌:C# 编译器通常可以根据传入参数的类型自动推断出 T的具体类型,因此调用时往往可以省略 <int>。
  • ‌默认值 ‌:default(T) 对于引用类型返回 null,对于值类型返回其零值(如 0, false)。

2. 泛型类 (Generic Classes)

泛型类将类型参数提升到类级别,类中的字段、属性、方法返回值及参数均可使用该类型参数。

cs 复制代码
public class SimpleGenericClass<T>
{
    public T Field;

    public SimpleGenericClass(T value)
    {
        Field = value;
    }

    public void Display()
    {
        Console.WriteLine($"Value: {Field}, Type: {typeof(T).Name}");
    }
}

// 使用示例
var stringBox = new SimpleGenericClass<string>("Hello");
stringBox.Display(); // Output: Value: Hello, Type: String

var intBox = new SimpleGenericClass<int>(42);
intBox.Display();    // Output: Value: 42, Type: Int32
  • 构造泛型类 ‌:当实例化 SimpleGenericClass<string> 时,CLR 会创建一个特定的封闭构造类型,其中所有的 T 都被替换为 string。
  • ‌类型安全‌:一旦指定了类型,后续操作必须遵循该类型约束,避免了装箱/拆箱操作带来的性能损耗和运行时类型错误。

3. 泛型接口 (Generic Interfaces)

泛型接口定义了契约,允许实现类针对特定类型提供具体实现,常用于集合框架和服务抽象。

cs 复制代码
public interface IGenericInterface<T>
{
    T GetItem();
    void SetItem(T item);
}

public class StringRepository : IGenericInterface<string>
{
    private string _data;

    public string GetItem() => _data;
    public void SetItem(string item) => _data = item;
}

public class GenericRepository<T> : IGenericInterface<T>
{
    private T _data;
    public T GetItem() => _data;
    public void SetItem(T item) => _data = item;
}
  • 接口可以是泛型的,实现类可以选择继续保留泛型(如 GenericRepository<T>),也可以指定具体类型(如 StringRepository)。
  • .NET 标准库中的 IEnumerable<T>, ICollection<T>, IList<T> 都是典型的泛型接口。

4. 泛型委托 (Generic Delegates)

泛型委托允许定义具有类型参数的方法签名,广泛用于事件处理和回调机制。.NET 内置了 Action<T> 和 Func<T, TResult>。

cs 复制代码
// 自定义泛型委托
public delegate T ResultDelegate<T>(T input);

// 使用内置泛型委托
public class DelegateExample
{
    public static void Process<T>(T data, Action<T> action)
    {
        action(data);
    }

    public static TResult Transform<T, TResult>(T data, Func<T, TResult> func)
    {
        return func(data);
    }
}

// 使用示例
DelegateExample.Process("Hello", s => Console.WriteLine(s.ToUpper()));

int length = DelegateExample.Transform("World", s => s.Length);
Console.WriteLine(length); // Output: 5
  • Action<T>:无返回值,接受一个或多个参数。
  • Func<T, TResult>:有返回值,最后一个类型参数为返回类型。
  • 泛型委托极大地简化了 LINQ 查询和异步编程中的回调定义。

5. 泛型约束 (Generic Constraints)

默认情况下,泛型类型参数 T 可以是任何类型。为了在泛型内部调用特定成员或保证类型特性,需要使用 where 关键字添加约束。

常用约束类型:

约束 说明
where T : struct T 必须是值类型(包括 enum)
where T : class T 必须是引用类型
where T : new() T 必须有一个公共的无参构造函数
where T : BaseClass T 必须继承自 BaseClass
where T : Interface T 必须实现 Interface
cs 复制代码
public class ConstraintExample
{
    // T 必须是引用类型,实现 ICollection<string>,且有无参构造函数
    public static TOutput Factory<TOutput>() 
        where TOutput : class, ICollection<string>, new()
    {
        TOutput collection = new TOutput();
        collection.Add("Item1");
        return collection;
    }

    // T 必须是值类型
    public static void PrintValueType<T>(T value) where T : struct
    {
        Console.WriteLine($"Value: {value}, IsValueType: true");
    }
}
  • 约束增加了泛型内部可执行的操作数量。例如,只有添加了 new() 约束,才能使用 new T()。
  • 多个约束可以同时存在,new() 约束必须放在最后。

6. 泛型原理 (Generic Principles)

泛型不仅仅是语法糖,它在 CLR(公共语言运行时)层面有专门的支持。

编译时处理:

  • C# 编译器将泛型代码编译为 IL(中间语言),此时类型参数仍为占位符。
  • JIT(即时编译器)在运行时根据实际使用的类型参数生成具体的本地代码。

代码共享与特异性:

  • 引用类型:对于所有引用类型(如 List<string>, List<object>),CLR 通常共享同一份本机代码,因为引用在内存中的表示形式相同(指针)。
  • 值类型:对于每个不同的值类型(如 List<int>, List<double>),CLR 会生成独立的本机代码副本,因为值类型的大小和布局不同。

优势:

  • 类型安全:编译时检查类型匹配,避免运行时 InvalidCastException。
  • 性能提升:避免了值类型的装箱(Boxing)和拆箱(Unboxing)操作。
  • 代码复用:一套逻辑适用于多种类型。

7. 扩展泛型缓存 (Extended Generic Cache)

在 ASP.NET Core 中,泛型常与依赖注入和缓存结合使用,以提供类型安全的缓存访问。

cs 复制代码
using Microsoft.Extensions.Caching.Memory;

public static class CacheExtensions
{
    // 泛型方法:获取缓存,如果不存在则通过工厂函数创建并缓存
    public static T GetOrCreate<T>(this IMemoryCache cache, string key, Func<ICacheEntry, T> factory)
    {
        if (!cache.TryGetValue(key, out T result))
        {
            var entry = cache.CreateEntry(key);
            result = factory(entry);
            entry.SetValue(result);
            
            // 可选:设置过期策略
            entry.SetSlidingExpiration(TimeSpan.FromMinutes(5));
            
            entry.Dispose(); // 必须释放以提交缓存项
        }
        return result;
    }
}

// 在 Controller 或 Service 中使用
public class DataService
{
    private readonly IMemoryCache _cache;

    public DataService(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }

    public List<string> GetUsers()
    {
        // 类型安全地获取缓存,无需手动转换类型
        return _cache.GetOrCreate("users_key", entry => 
        {
            // 模拟耗时操作
            return new List<string> { "User1", "User2" };
        });
    }
}
  • 通过泛型扩展方法,调用者无需关心缓存内部存储的是 object,直接获得强类型的结果。
  • IMemoryCache 是 ASP.NET Core 推荐的内存缓存接口,支持依赖注入。

8. 协变与逆变 (Covariance and Contravariance)

协变和逆变描述了泛型类型参数在继承关系中的转换规则,主要应用于接口和委托。

  • **协变 (Covariance, out)**‌:允许将更派生的类型替换为基类型。适用于输出位置(返回值)。
  • ‌**逆变 (Contravariance, in)**‌:允许将基类型替换为更派生的类型。适用于输入位置(参数)。
cs 复制代码
// 协变接口:T 只用于输出
public interface ICovariant<out T>
{
    T GetItem();
}

// 逆变接口:T 只用于输入
public interface IContravariant<in T>
{
    void SetItem(T item);
}

// 测试类
public class Base { }
public class Derived : Base { }

public class VarianceTest
{
    public static void Run()
    {
        // 协变:可以将 ICovariant<Derived> 赋值给 ICovariant<Base>
        ICovariant<Derived> derivedSource = new CovariantImpl<Derived>();
        ICovariant<Base> baseTarget = derivedSource; // 合法

        // 逆变:可以将 IContravariant<Base> 赋值给 IContravariant<Derived>
        IContravariant<Base> baseSource = new ContravariantImpl<Base>();
        IContravariant<Derived> derivedTarget = baseSource; // 合法
    }
}

public class CovariantImpl<T> : ICovariant<T>
{
    public T GetItem() => default(T);
}

public class ContravariantImpl<T> : IContravariant<T>
{
    public void SetItem(T item) { }
}

协变和逆变仅适用于引用类型,不适用于值类型。且类型参数只能出现在输出或输入位置,不能同时出现在两者中(除非不使用方差修饰符)。

相关推荐
有志冬瓜1 小时前
.NET Framework全部版本离线下载安装教程
安全·.net·软件需求
IT规划师2 小时前
Redis 数据类型全解 - Set 的三个真相:去重、标签、共同好友(四)
redis·.net
椒颜皮皮虾྅1 天前
让文档不止于文字:DeploySharp + PP-Structure,打通版面分析与表格重建
开发语言·学习·c#·.net·cuda
玩泥巴的1 天前
可能是最现代化的微信生态 .NET 服务端 SDK
ai·微信·.net·aot
椒颜皮皮虾྅1 天前
一套 C# 接口,解锁四类视觉任务:DeploySharp 分类、分割、关键点与旋转框实战
开发语言·分类·c#
罗迪尼亚的熔岩1 天前
联合kuka机器人, 海康visonmaster 的手眼9点标定方法
机器人·c#·工业相机·标定
她说彩礼65万1 天前
C# TaskCompletionSource
java·开发语言·c#