Reputation: 535
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!!
}
}
Upvotes: 2
Views: 693
Reputation: 622
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:
SetValue
simply calls this delegate with the destination object and valueFieldInfo
/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;
}
}
}
Upvotes: 0