You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#运行时确定类型的静态泛型类内存分配机制问询

静态泛型类在运行时类型确定后的内存分配机制

问题背景

已知static关键字的特性:

  • 静态成员的内存在整个应用程序中仅分配一次。
  • 静态类的字段和方法在程序集加载时即存入内存。

我实现了一个用于缓存反射密集型代码的C#静态泛型类,类型参数TEntity在运行时确定,简化代码如下:

using System.Reflection;
using System.Linq.Expressions;

internal sealed record PropertyAccessor(string Name, Func<object, object> Get, Action<object, object> Set);

internal static class EntityType<TEntity> where TEntity : class, IEntity
{
    public static IEnumerable<PropertyAccessor> PropertyAccessors => LazyPropertyAccessors.Value;

    private static readonly Lazy<IList<PropertyAccessor>> LazyPropertyAccessors = new(() => GetPropertiesAccessor(typeof(TEntity)));

    private static IList<PropertyAccessor> GetPropertiesAccessor(IReflect type)
    {
        return type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(p => p.CanRead && p.CanWrite)
            .Select(GetEntityProperty)
            .ToArray();
    }

    private static PropertyAccessor GetEntityProperty(PropertyInfo property)
    {
        return new PropertyAccessor(property.Name, property.GetGetMethod().BuildGetAccessor(), property.GetSetMethod().BuildSetAccessor());
    }
}

public static class ReflectionTypeExtensions
{
    public static Func<object, object> BuildGetAccessor(this MethodInfo method)
    {
        if (method == null)
            throw new ArgumentNullException(nameof(method));

        var declaringType = method.DeclaringType;

        if (declaringType == null)
            throw new NullReferenceException(nameof(method.DeclaringType));

        var obj = Expression.Parameter(typeof(object));

        var getter = Expression.Lambda<Func<object, object>>(
            Expression.Convert(Expression.Call(Expression.Convert(obj, declaringType), method), typeof(object)),
            obj);

        return getter.Compile();
    }

    public static Action<object, object> BuildSetAccessor(this MethodInfo method)
    {
        if (method == null)
            throw new ArgumentNullException(nameof(method));

        var declaringType = method.DeclaringType;

        if (declaringType == null)
            throw new NullReferenceException(nameof(method.DeclaringType));

        var obj = Expression.Parameter(typeof(object));
        var value = Expression.Parameter(typeof(object));

        var expr = Expression.Lambda<Action<object, object>>(Expression.Call(Expression.Convert(obj, declaringType),
                method,
                Expression.Convert(value, method.GetParameters()[0].ParameterType)),
            obj,
            value);

        return expr.Compile();
    }
}

代码各部分说明

  • PropertyAccessor:用于存储对象属性信息的record类型。
  • IEntity:标记接口,代表数据库中的实体模型。
  • BuildGetAccessor:扩展方法,创建可获取属性值的Func<object, object>委托。
  • BuildSetAccessor:扩展方法,创建可设置属性值的Action<object, object>委托。
  • PropertyAccessors:返回TEntity类的所有属性访问器列表。

核心问题

当TEntity在运行时被解析时,EntityType<TEntity>的静态成员的内存是如何、何时分配的?静态泛型类的内存分配机制具体是怎样的?


回答

核心原理:每个封闭构造泛型类型是独立的CLR类型

CLR会把每个不同类型参数的EntityType<TEntity>(比如EntityType<User>、EntityType<Order>)视为完全独立的类型,每个封闭构造类型都拥有一套专属的静态成员,彼此的内存空间完全隔离,互不干扰。

静态成员的内存分配时机

  1. 并非程序集加载时就分配:
    普通非泛型静态类的静态成员会在程序集加载时初始化,但静态泛型类的封闭构造类型不同——只有当该封闭类型第一次被主动引用时(比如访问PropertyAccessors属性、调用静态方法,或者通过反射创建/访问该类型的成员),CLR才会为其静态成员分配内存,并执行静态初始化逻辑。

  2. Lazy<T>字段的延迟初始化细节:
    你代码中的LazyPropertyAccessors是static readonly的Lazy<IList<PropertyAccessor>>实例:

    • 当第一次访问PropertyAccessors触发LazyPropertyAccessors.Value时,才会执行GetPropertiesAccessor方法,生成对应的PropertyAccessor列表,此时这些对象的内存才会被实际分配。
    • 初始化完成后,该封闭类型的静态成员会一直驻留在内存中,直到应用程序域卸载(比如进程结束)——这符合“静态成员内存仅分配一次”的特性,但这里的“一次”是针对单个封闭构造类型而言的,不同TEntity对应的静态成员是独立分配、独立存储的。

针对运行时动态确定TEntity的场景

即使TEntity是运行时通过反射等方式动态确定的(比如通过Type对象创建EntityType<T>的实例并访问静态成员),CLR依然会为这个动态生成的封闭构造类型创建独立的静态成员实例,初始化逻辑和编译时确定的泛型类型完全一致。这种机制正好适配你的反射缓存需求:每个实体类型的属性访问器只会被生成一次,后续访问直接复用缓存结果,彻底避免重复的反射和表达式编译开销。


内容的提问来源于stack exchange,提问作者Christopher Fontaine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 08:45:02