2

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.

2
  • I'm afraid performance and reflection will never get along Commented Sep 4, 2021 at 8:32
  • @AndrewSilver while I do usually agree with that, I do think there might be room for improvement in my implementation. Even if I am to move away from reflection. I mean, it shouldn't be a matter of me chosing between high memory usage from a class with a Dictionary<string, object> or low access performance on reflection.emit custom type. There should be another way, right? Commented Sep 6, 2021 at 13:08

1 Answer 1

4

You can use expression trees to generate the delegates which will be almost twice as fast as the code you provided:

using LinqExpression = System.Linq.Expressions.Expression;

private static readonly ConcurrentDictionary<Type, PropertyHelper[]> CacheExpressionTree = new ConcurrentDictionary<Type, PropertyHelper[]>();

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

    var instance = LinqExpression.Parameter(typeof(MyClass));

    Func<MyClass, object> getter = null;
    var getMethod = property.GetGetMethod();
    if (getMethod != null)
    {
      getter =
      LinqExpression.Lambda<Func<MyClass, object>>(
        LinqExpression.Convert(
          LinqExpression.Call(
            LinqExpression.Convert(instance, eventLogCustomType),
            getMethod),
          typeof(object)),
        instance)
      .Compile();
    }

    Action<MyClass, object> setter = null;
    var setMethod = property.GetSetMethod();
    if (setMethod != null)
    {
      var parameter = LinqExpression.Parameter(typeof(object));
      setter =
      LinqExpression.Lambda<Action<MyClass, object>>(
        LinqExpression.Call(
          LinqExpression.Convert(instance, eventLogCustomType),
          setMethod,
          LinqExpression.Convert(parameter, propertyType)),
        instance, parameter)
      .Compile();
    }

    return new PropertyHelper
    {
      Name = property.Name,
      Getter = getter,
      Setter = setter,
    };
  }).ToArray());
}

You may also look at FastExpressionCompiler since it will give a little bit more performance:

using LightExpression = FastExpressionCompiler.LightExpression.Expression;

private static readonly ConcurrentDictionary<Type, PropertyHelper[]> CacheExpressionTreeFast = new ConcurrentDictionary<Type, PropertyHelper[]>();

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

    var instance = LightExpression.Parameter(eventLogCustomType);

    Func<MyClass, object> getter = null;
    var getMethod = property.GetGetMethod();
    if (getMethod != null)
    {
      getter = LightExpression.Lambda<Func<MyClass, object>>(LightExpression.Call(instance, getMethod), instance).CompileFast();
    }

    Action<MyClass, object> setter = null;
    var setMethod = property.GetSetMethod();
    if (setMethod != null)
    {
      var parameter = LightExpression.Parameter(propertyType);
      setter = LightExpression.Lambda<Action<MyClass, object>>(LightExpression.Call(instance, setMethod, parameter), instance, parameter).CompileFast();
    }

    return new PropertyHelper
    {
      Name = property.Name,
      Getter = getter,
      Setter = setter,
    };
  }).ToArray());
}

I ran a benchmark comparing the different methods, here are the results:

|                                Method |      Mean |     Error |    StdDev |    Median |
|-------------------------------------- |----------:|----------:|----------:|----------:|
|                          DirectSetter |  5.529 ns | 0.0076 ns | 0.0059 ns |  5.529 ns |
|                          DirectGetter |  3.127 ns | 0.0092 ns | 0.0072 ns |  3.126 ns |
|                   GetPropertiesSetter | 15.897 ns | 0.2153 ns | 0.1798 ns | 15.872 ns |
|                   GetPropertiesGetter | 15.393 ns | 0.3532 ns | 1.0191 ns | 14.942 ns |
|     GetPropertiesExpressionTreeSetter |  7.200 ns | 0.1357 ns | 0.1133 ns |  7.174 ns |
|     GetPropertiesExpressionTreeGetter |  6.095 ns | 0.1034 ns | 0.0863 ns |  6.070 ns |
| GetPropertiesExpressionTreeFastSetter |  6.621 ns | 0.0101 ns | 0.0084 ns |  6.622 ns |
| GetPropertiesExpressionTreeFastGetter |  5.918 ns | 0.0167 ns | 0.0130 ns |  5.916 ns |
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much about your answer! I'll try to implement it and I'll come back with your points, hahah! Thanks again!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.