vendredi 3 septembre 2021

Improve performance on getting/setting values to properties on objects created from class created by reflection

I'm having some problems to find a solution to this very specific issue. I have a class, let's call it "MyClass", that serves as base to many other implementations we create via reflection on runtime. These implementations we create are classes that inherit "MyClass" and add a bunch of new string, double and DateTime properties to it (let's call them MyClassCustom). To access the values of those new properties on MyClassCustom, we have some methods on our base class that do some reflection shenanigans to get the data (more specifically the SetValue and GetValue from GetType().GetProperty(name)).

Since we're trying to do bulk operations on those implementations, we have a concern on performance. I've navigated to some other issues like this one and found out that if I used delegates, the performance would be almost the same as if I used the direct accessors (200ms average for 10mi items versus previous 4~10s average) and it would look somewhat like this:

Type type = myClassCustomObject.GetType();
Action<MyClass, object> setter = (Action<MyClass, double>)Delegate.CreateDelegate(typeof(Action<MyClass, double>), null, type.GetProperty(propertyName).GetSetMethod());
Func<MyClass, double> getter = (Func<MyClass, double>)Delegate.CreateDelegate(typeof(Func<MyClass, object>), null, type.GetProperty(propertyName).GetGetMethod());

var value = getter(myClassImplementation);
setter(myClassImplementation, value + 1);

However this don't work (which is because I can't convert Action<MyClassImplementation, double> to Action<MyClass, double>) and when I try to wrap this up, I lose all the performance as if I was still doing the GetProperty(name).SetValue. See the code we tested below:

public class PropertyHelper {
  private static readonly ConcurrentDictionary<Type, PropertyHelper[]> Cache = new ConcurrentDictionary<Type, PropertyHelper[]>();
  private static readonly MethodInfo CallInnerGetDelegateMethod = typeof(PropertyHelper).GetMethod(nameof(CallInnerGetDelegate), BindingFlags.NonPublic | BindingFlags.Static);
  private static readonly MethodInfo CallInnerSetDelegateMethod = typeof(PropertyHelper).GetMethod(nameof(CallInnerSetDelegate), BindingFlags.NonPublic | BindingFlags.Static);
  
  public string Name { get; set; }
  public Func<MyClass, object> Getter { get; set; }
  public Action<MyClass, object> Setter { get; set; }

  public static PropertyHelper GetProperty(Type type, string propName) {
    return GetProperties(type).FirstOrDefault(x => x.Name == propName);
  }

  public static PropertyHelper[] GetProperties(Type type) {
    return Cache.GetOrAdd(type, _ => type.GetProperties().Select(property => {
      Type eventLogCustomType = property.DeclaringType;
      Type propertyType = property.PropertyType;

      Func<MyClass, object> getter = null;
      var getMethod = property.GetGetMethod();
      if (getMethod != null) {
          Type getMethodDelegateType = typeof(Func<,>).MakeGenericType(eventLogCustomType, propertyType);
          Delegate getMethodDelegate = Delegate.CreateDelegate(getMethodDelegateType, null, getMethod);
          
          MethodInfo callInnerGenericGetMethodWithTypes = CallInnerGetDelegateMethod.MakeGenericMethod(eventLogCustomType, propertyType);
          getter = (Func<MyClass, object>)callInnerGenericGetMethodWithTypes.Invoke(null, new[] { getMethodDelegate });
      }

      Action<MyClass, object> setter = null;
      var setMethod = property.GetSetMethod();
      if (setMethod != null) {
          Type setMethodDelegateType = typeof(Action<,>).MakeGenericType(eventLogCustomType, propertyType);
          Delegate setMethodDelegate = Delegate.CreateDelegate(setMethodDelegateType, null, setMethod);

          MethodInfo callInnerGenericSetMethodWithTypes = CallInnerSetDelegateMethod.MakeGenericMethod(eventLogCustomType, propertyType);
          setter = (Action<MyClass, object>)callInnerGenericSetMethodWithTypes.Invoke(null, new[] { setMethodDelegate });
      }

      return new PropertyHelper {
          Name = property.Name,
          Getter = getter,
          Setter = setter,
      };
    }).ToArray());
  }
  
  private static Func<MyClass, object> CallInnerGetDelegate<TClass, TResult>(Func<TClass, TResult> innerDelegate)
    where TClass : MyClass {
    return instance => innerDelegate((TClass)instance);
  }

  private static Action<MyClass, object> CallInnerSetDelegate<TClass, TResult>(Action<TClass, TResult> innerDelegate)
    where TClass : MyClass {
    return (instance, value) => innerDelegate((TClass)instance, (TResult)value);
  }
}

I think I've exhausted my knowledge on this issue and don't know where else to look.

Thanks in advance.





Aucun commentaire:

Enregistrer un commentaire