The keyword static
has the following theses:
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?
- 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