2

I am in a situation where I need to hydrate a large number of DTOs of various classes using reflection, but I'd like to avoid boxing and unboxing which hurts performance. Any idea how?

Example to illustrate:

    public class Person {
      public int Age { get; set; }
    }
    var person = new Person();
    var ageProp = typeof(Person).GetProperty("Age");
    ageProp.SetValue(person , 13); // Causes boxing!!

EDIT

Here is a better example:

public void CreateAndHydrateEntity(Type entityType, List<(string PropName, int PropIndex) properties, SqlDataReader reader) {
    var entity = Activator.CreateInstance(entityType);
    foreach(var (propName, index) in properties) {
        var prop = entityType.GetProperty(propName);
        prop.SetValue(entity, reader[index]); // Causes boxing!!
    }
}
Ahmad Akra
  • 505
  • 4
  • 14
  • 4
    Why do you need this? What's your use case? Are you sure it hurts performance? – mtkachenko Jul 17 '19 at 11:03
  • 1
    If you create person like this `var person = new Person()` why do you need reflection? – mtkachenko Jul 17 '19 at 11:04
  • 1
    I recommend you lookup compiled expressions. You can compile setters/getters at run time once, cache them, and then use them directly. This will noticeably have better performance over normal reflection. – Sohaib Jundi Jul 17 '19 at 11:14
  • If you have to update "various classes", let all these classes implement an *interface*, say, `IAge` and then you can update via this interface, without any reflection / boxing. – Dmitry Bychenko Jul 17 '19 at 11:24
  • @mtkachenko I am building a mini special purpose ORM that loads data from the SQL database and hydrates C# objects with it. So I know the name of the property, the type of the property, and when the property is a value type (e.g. int) I wish there was a way to set it without this unboxing business – Ahmad Akra Jul 17 '19 at 11:57
  • @AAkra please try https://github.com/StackExchange/Dapper first – mtkachenko Jul 17 '19 at 12:09
  • Possible duplicate of [Can I set a value on a struct through reflection without boxing?](https://stackoverflow.com/questions/9927590/can-i-set-a-value-on-a-struct-through-reflection-without-boxing) – colosso Jul 17 '19 at 12:19
  • You probably want to call appropriate method on SqlDataReader. From GetInt32 to GetTimeSpan methods. You know the type, you know which to call. – dropoutcoder Jul 17 '19 at 12:22
  • @colosso it is not a duplicate, that question is about setting the property in a struct using reflection, I'm dealing with a normal class where the property itself is a value-type – Ahmad Akra Jul 17 '19 at 12:53
  • 1
    @dropoutcoder thank you, yes I can use GetXXX to retrieve the value in its proper type, but then the question is: how to set the property with that value without unboxing – Ahmad Akra Jul 17 '19 at 12:54
  • Get the set method of your property, create a delegate using that method and call it. But if you can not cast that delegate to the exact type you just pushed the boxing one layer up. – thehennyy Jul 17 '19 at 13:05
  • Alright, it turns out it isn't the boxing/unboxing that was causing the performance hit, but the reflection itself, I am getting the same performance hit even when setting reference types, so I have to find a way to circumvent reflection when hydrating my objects, I will look into some of the techniques suggested above (like compiled expressions) – Ahmad Akra Jul 17 '19 at 13:58
  • I think you can cache reflected properties for each type to gain performance. I guess those types are not changed in runtime and maybe they have base type. You can do early heavy lifting. Also using prototype for those types might help gain performance to avoid calling Activator.CreateInstance method, but this I am not sure if makes reasonable difference. – dropoutcoder Jul 18 '19 at 10:43
  • 1
    Here is an Expression-based approach, have a look. https://stackoverflow.com/a/17669142/4691615 – Sasha Yakobchuk Dec 19 '22 at 14:27

1 Answers1

0

Here's what I did for my needs, hope it helps someone.

tldr: I create "mappings" that I keep in a dictionary for each property (used as an expression, no property names as strings => easy to refactor). Using the mapping looks like

class ExampleClass
{
    public int TestProperty1 { get; set; }
}
var m = new Mapping<ExampleClass, int>(dest => dest.TestProperty1);
var destObj = new ExampleClass();
m.SetValue(destObj, 1);

And the Mapping class:

  • the expression parameter accepts a property or a field that must belong to the respective (destination) type
  • for properties, the default setter is saved as a delegate
  • for fields a setter is created from IL and saved as a delegate
  • SetValue simply calls this delegate with the destination object and value
  • I left the version that uses FieldInfo/PropertyInfo default SetValue method which takes objects as parameters so it causes boxing, in case anyone wants to do some benchmarks (hint: the difference is HUGE!)

Code below:

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

public class Mapping<TDst, TValue>
{
    private readonly MemberInfo dstMember;
    private readonly Action<TDst, TValue> dstSetter = null!;

    private static MemberInfo DestinationMember<T, TMember>(Expression<Func<T, TMember>> expression)
    {
        if (expression.Body is not MemberExpression memberExpression)
            throw new ArgumentException($"Expression '{expression.Name}' does not refer to a field or property.");

        var type = typeof(T);

        if (memberExpression.Member.ReflectedType == null ||
            (type != memberExpression.Member.ReflectedType &&
             !type.IsSubclassOf(memberExpression.Member.ReflectedType)))
            throw new ArgumentException(
                $"Expresion '{memberExpression.Member.Name}' refers to a property that is not from type {type}.");
        return memberExpression.Member;
    }

    public Mapping(Expression<Func<TDst, TValue>> dstExp)
    {
        dstMember = DestinationMember(dstExp);
        if (dstMember is PropertyInfo { CanWrite: false }) throw new ArgumentException("Destination is read-only");
        switch (dstMember)
        {
            case FieldInfo fi:
                var dynamicMethod = new DynamicMethod(string.Empty, typeof(void),
                    new[] { typeof(TDst), typeof(TValue) }, true);
                var ilGenerator = dynamicMethod.GetILGenerator();
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldarg_1);
                ilGenerator.Emit(OpCodes.Stfld, fi);
                ilGenerator.Emit(OpCodes.Ret);
                dstSetter = (Action<TDst, TValue>)dynamicMethod.CreateDelegate(typeof(Action<TDst, TValue>));
                break;
            case PropertyInfo pi:
                var setter = pi.GetSetMethod(true)!;
                dstSetter = (Action<TDst, TValue>)Delegate.CreateDelegate(typeof(Action<TDst, TValue>), setter);
                break;
        }
    }

    public void SetValue(TDst destination, TValue value) => dstSetter(destination, value);

    public void SetValueWithBoxing(TDst destination, TValue value)
    {
        switch (dstMember)
        {
            case FieldInfo fi:
                fi.SetValue(destination, value);
                break;
            case PropertyInfo pi:
                pi.SetValue(destination, value);
                break;
        }
    }
}
Stefan Balan
  • 592
  • 4
  • 21
  • _"for fields a setter is created from IL and saved as a delegate"_ - any particular reason to prefer this to `Expression.PropertyOrField` + `Expression.Assign` (though maybe perf of using IL emit can be better)? – Guru Stron May 23 '23 at 09:34