Могу ли я установить свойство структуры с помощью выражений?
У меня есть следующий метод, который устанавливает значение для данного PropertyInfo
на данный TInstance
, Это чтобы избежать неэффективности рефлексии.
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();
}
Итак, учитывая следующую модель:
public sealed class PersonClass
{
public string Name {get; set;}
}
Я могу установить Name
с помощью:
var person = new PersonClass();
var nameProp = person.GetType().GetProperties().Where(p => p.Name == "Name").First();
var nameSetter = CreateSetter<PersonClass>(nameProp);
nameSetter(person, "Foo");
Это все хорошо, однако, если я попробую метод с struct
например:
public struct PersonStruct
{
public string Name {get; set;}
}
Имя всегда null
, Я подозреваю, что бокс / распаковка кусает меня как-то.
На самом деле, если я использую FastMember
такое же поведение проявляется при использовании:
PersonStruct person = new PersonStruct();
var accessor = TypeAccessor.Create(person.GetType());
accessor[person, "Name"] = "Foo";
Однако, когда я бокс person
как object
затем FastMember
может правильно установить значение:
object person = new PersonStruct();
var accessor = TypeAccessor.Create(person.GetType());
accessor[person, "Name"] = "Foo";
Любые идеи, как я могу справиться с этим боксом внутри CreateSetter
когда TInstance
такое тип значения?
2 ответа
Как отмечено в комментариях, вы действительно не должны создавать изменяемые структуры. Однако, чтобы ответить на вопрос, структуры являются типами значений, и поэтому копия вашей структуры передается в Action
и, следовательно, первоначальная личная ценность не изменяется.
Вам нужен способ пройти struct
по ссылке. Однако выражения не поддерживают создание "методов", которые принимают параметры по ссылке.
Что вы можете сделать, это использовать DynamicMethod
Класс, чтобы сделать что-то вроде этого:
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> ));
}
Мы должны были создать StructSetter
делегировать, потому что стандарт Action
делегаты не поддерживают передачу по ссылке.
Не забудьте кэшировать делегат, иначе затраты на компиляцию замедляют работу вашего приложения.
Вам нужно выражение, которое создает делегат, который принимает аргумент by-ref, чтобы он влиял на передаваемую структуру, а не на копию. Например:
public struct PersonStruct
{
public string Name {get; set;}
}
delegate void FirstByRefAction<T1, T2>(ref T1 arg1, T2 arg2);
void Main()
{
ParameterExpression par1 = Expression.Parameter(typeof(PersonStruct).MakeByRefType());
ParameterExpression par2 = Expression.Parameter(typeof(string));
FirstByRefAction<PersonStruct, string> setter = Expression.Lambda<FirstByRefAction<PersonStruct, string>>(
Expression.Assign(Expression.Property(par1, "Name"), par2),
par1, par2
).Compile();
PersonStruct testStruct = new PersonStruct();
setter(ref testStruct, "Test Name");
Console.Write(testStruct.Name); // outputs "Test Name"
}
Это чтобы избежать неэффективности рефлексии.
Обратите внимание, что типы выражений, вызывающих методы и свойства, в основном используют внутреннее отражение. В случае интерпретированных выражений они делают это при каждом вызове, в случае скомпилированных выражений IL отражение все еще используется на этапе компиляции.