在运行时确定类型时,C# 中的静态泛型类的内存分配如何工作?

How does memory allocation work for static generic classes in C# when the type is determined at runtime?

提问人:Christopher Fontaine 提问时间:11/13/2023 更新时间:11/13/2023 访问量:45

问:

该关键字具有以下论点:static

  • 静态成员的内存为整个 应用。
  • 加载程序集后,静态类字段和方法将立即存储在内存中。

因此,我在 C# 中有一个静态泛型类,它包含静态成员,并由运行时确定的类型参数化。我使用静态泛型类来缓存反射较重的代码。下面是代码的简化版本:

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是用于存储以下信息的记录 对象的属性。
  • IEntity是一个标记接口,指示数据库中的实体模型。
  • BuildGetAccessor是一种扩展方法,用于创建允许您获取属性值的委托。Func<object, object>
  • BuildSetAccessor是一个扩展方法,用于创建允许设置属性值的委托。Action<object, object>
  • PropertyAccessors返回类的所有属性的列表。TEntity

我的问题是:当类型在运行时确定时,静态泛型类的内存分配如何工作?具体来说,如何以及何时为静态成员分配内存 when is resolved at runtime?Entity<TEntity>TEntity

C# 泛型静态

评论


答:

0赞 JonasH 11/13/2023 #1
  • 静态成员的内存为整个应用程序分配一次。
  • 加载程序集后,静态类字段和方法将立即存储在内存中。

我认为这两者都是不正确的。我的理解是,静态类字段有时会在第一次使用之前分配,确切的时间明确留给运行时。以同样的方式,我认为如果可以证明静态成员不再使用,则可以收集它们,但我不确定 GC 会费心跟踪这一点。

在运行时确定类型时,静态泛型类的内存分配如何工作?具体来说,在运行时解析 TEntity 时,如何以及何时为 Entity 的静态成员分配内存?

与常规类相同,在使用之前的某个时间。最有可能的是,这将在需要之前完成,即“及时”。因此,当抖动编译首次使用的方法时,它将编译类型,并执行任何所需的初始化等。Entity<MySpecificType>

请记住语言规范和运行时之间的差异。该语言只指定行为,而不指定如何实现此特定行为。一个符合要求的实现可以做任何它想做的事情,但某些功能,如泛型,很难在没有抖动的情况下完全实现。

John Skeet 在 .NET 4.0 中的类型初始化更改中写了一些关于类型初始化的文章

评论

0赞 Christopher Fontaine 11/17/2023
非常感谢,您的回答和文章消除了我的疑虑。从您的文章来看,我引用的关于静态成员的论点似乎仅适用于 .NET 3.5。在 .NET 4 及更高版本中,行为已更改。