2017-01-10 12 views
1

У меня есть следующий метод, который устанавливает значение для данного 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 является типом значений?

+0

Компиляция такого выражения, скорее всего, будет * более дорогим, чем отражение, в большинстве контекстов. – Servy

+0

Боковое примечание: только некоторые чтения - http://stackoverflow.com/questions/441309/why-are-mutable-structs-evil –

+1

Структуры передаются по значению. Поэтому, когда вы передаете «sturct» в действие, копия этой структуры передается. Думаю, вам нужен способ пройти по ссылке. –

ответ

1

Как отмечено в комментариях, вы действительно не должны создавать изменчивые структуры. Однако, чтобы ответить на вопрос, структуры являются типами значений, и поэтому копия вашей структуры передается в 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 делегаты не поддерживают передачу по ссылке.

Не забывайте кэшировать делегата, иначе стоимость компиляции замедлит ваше приложение.

+0

«Однако выражения не поддерживают создание« методов », которые принимают параметры по ссылке.». Да, да. –

+1

Я этого не знал. Благодарю. –

+0

Конечный результат довольно близок к тому, что делает ваш код, когда выражение скомпилировано IL. При интерпретации он делает что-то близко к коду в вопросе, а затем записывает обратно в параметр, который менее эффективен, но может использоваться, когда генерация IL недоступна. –

1

Вам нужно выражение, которое создает делегат, который принимает аргумент 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-скомпилированных выражений выражение все еще используется на этапе компиляции.

+0

Спасибо, Джон, пытаясь переписать мой метод, используя ваше предложение. – MaYaN