vendredi 10 novembre 2023

Comparing characteristics of accessing class properties in C# via a static generic class and static class with ConcurrentDictionary

I'm using C#, and I have two approaches for accessing class properties: through Entity<Unit>.PropertyAccessors and EntityManager.PropertyAccessor<Unit>(). Both options provide access to class metadata for working with properties. I'm interested in the differences in their performance, memory usage, thread safety, and other relevant aspects.

What are the key distinctions between Entity<Unit>.PropertyAccessors and EntityManager.PropertyAccessor<Unit>() in terms of performance, memory usage, thread safety, and other critical aspects? Is there a preferred option in each of these categories? Are there any potential pitfalls or gotchas that I should be aware of when using either of these implementations?

I'm using a ThreadSafeDictionary with ConcurrentDictionary<TKey, Lazy<TValue>> under the hood.

Below is the code I'm using for a more detailed context:

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

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

internal static class Entity<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());
    }
}

internal static class EntityManager
{
    private static readonly IThreadSafeDictionary<Type, IEnumerable<PropertyAccessor>> EntitiesPropertiesAccessor;

    static EntityManager()
    {
        EntitiesPropertiesAccessor = new ThreadSafeDictionary<Type, IEnumerable<PropertyAccessor>>();
    }

    public static IEnumerable<PropertyAccessor> PropertiesAccessor<TEntity>() where TEntity : class, IEntity
    {
        return EntitiesPropertiesAccessor.GetOrAdd(typeof(TEntity), GetPropertiesAccessor);
    }

    private static IEnumerable<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();
    }
}




Aucun commentaire:

Enregistrer un commentaire