I have the following method which sets the value for the given PropertyInfo
on the given TInstance
. This is to avoid the inefficiency of reflection.
public static Action<TInstance, object> CreateSetter<TInstance>(PropertyInfo propertyInfo, bool includeNonPublic = false)
{
var setMethod = propertyInfo.GetSetMethod(includeNonPublic);
var instance = Expression.Parameter(typeof(TInstance), "instance");
var value = Expression.Parameter(typeof(object), "value");
var valueCast = !propertyInfo.PropertyType.IsValueType
? Expression.TypeAs(value, propertyInfo.PropertyType)
: Expression.Convert(value, propertyInfo.PropertyType);
return Expression.Lambda<Action<TInstance, object>>(
Expression.Call(instance, setMethod, valueCast), instance, value).Compile();
}
So given the following model:
public sealed class PersonClass
{
public string Name {get; set;}
}
I can set the Name
using:
var person = new PersonClass();
var nameProp = person.GetType().GetProperties().Where(p => p.Name == "Name").First();
var nameSetter = CreateSetter<PersonClass>(nameProp);
nameSetter(person, "Foo");
This is all good however if I try the method with a struct
e.g.:
public struct PersonStruct
{
public string Name {get; set;}
}
The name is always null
. I suspect boxing/unboxing is biting me somehow.
In fact if I use FastMember
the same behavior exhibits when using:
PersonStruct person = new PersonStruct();
var accessor = TypeAccessor.Create(person.GetType());
accessor[person, "Name"] = "Foo";
However when I box the person
as object
then FastMember
is able to set the value correctly:
object person = new PersonStruct();
var accessor = TypeAccessor.Create(person.GetType());
accessor[person, "Name"] = "Foo";
Any ideas how I can handle this boxing inside the CreateSetter
for when TInstance
is a value type?
As noted in the comments, you really shouldn't create mutable structs. However, to answer the question, structs are value types and therefore a copy of your struct is passed to the Action
and therefore the original person value is not changed.
You need a way to pass the struct
by reference. However, expressions do not support creating "methods" that take parameters by reference.
What you can do is use the DynamicMethod
class to do something like this:
public delegate void StructSetter<TInstance>(ref TInstance instance, object value) where TInstance : struct;
public StructSetter<TInstance> CreateSetter<TInstance>(
PropertyInfo propertyInfo,
bool includeNonPublic = false) where TInstance : struct
{
DynamicMethod method =
new DynamicMethod(
"Set",
typeof(void),
new [] { typeof(TInstance).MakeByRefType(), typeof(object )},
this.GetType());
var generator = method.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod(includeNonPublic));
generator.Emit(OpCodes.Ret);
return (StructSetter<TInstance>)method.CreateDelegate(typeof (StructSetter<TInstance> ));
}
We had to create a StructSetter
delegate because the standard Action
delegates do not support passing by reference.
Don't forget to cache the delegate or otherwise the cost of compiling is going to slow down your application.