Search code examples
c#genericsstatic

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


The keyword static has the following theses:

  • Memory for static members is allocated once for the entire application.
  • Static class fields and methods are stored in memory as soon as the assembly is loaded.

So i have a static generic class in C# that contains static members and is parameterized by a type determined at runtime. I use static generic classes for caching reflection-heavy code. Here's a simplified version of the code:

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 is a record designed to store information about the properties of an object.
  • IEntity is a marker interface indicating an entity model from a database.
  • BuildGetAccessor is an extension method that creates a Func<object, object> delegate that allows you to get the value of a property.
  • BuildSetAccessor is an extension method that creates an Action<object, object> delegate that allows setting the value of a property.
  • PropertyAccessors returns a list of all properties of the TEntity class.

My question is: How does memory allocation work for a static generic class when the type is determined at runtime? Specifically, how and when is memory allocated for the static members of Entity<TEntity> when TEntity is resolved at runtime?


Solution

    • Memory for static members is allocated once for the entire application.
    • Static class fields and methods are stored in memory as soon as the assembly is loaded.

    I believe both of these to be incorrect. My understanding is that static class fields are allocated sometimes before the first usage, with the exact time being explicitly left up to the runtime. In the same manner I think static members could be collected if they can be proved to not be used anymore, but I'm not sure the GC bothers to track this.

    How does memory allocation work for a static generic class when the type is determined at runtime? Specifically, how and when is memory allocated for the static members of Entity when TEntity is resolved at runtime?

    The same way a regular class, sometime before it is used. Most likely this will be done just before it is needed, thereof "just in time". So when the jitter compiles a method that uses Entity<MySpecificType> for the first time, it will compile the type, and do any required initialization etc.

    Keep in mind the differences between the language specification and the runtime. The language only specifies behavior, not how to achieve this specific behavior. A conforming implementation could do whatever it wants, but some features, like generics, are difficult to implement fully without a jitter.

    John Skeet writes a bit about type initialization in Type initialization changes in .NET 4.0