2016-11-15 3 views
0

Я реализую испущенный обработчик свойства для моего объекта POCO, содержащий виртуальные автоматические свойства, и у меня есть код, который работает до того момента, когда исправляется свойствоchanged когда я изменяю базовое свойство. Причиной этого является то, что я делюсь объектом POCO с сервером (лучше или хуже), где я буду отправлять измененные объекты на сервер. Я не могу украсить объект POCO атрибутами (поскольку сервер также будет иметь эти декораторы, поскольку мы разделяем общий класс), и я не могу использовать сторонние инструменты, такие как Fody или PostSharp из-за политик. Мне нужно отслеживать, был ли объект изменен, и я застрял на этом.IL Emit - установить существующее свойство с логическим значением перед notifypropertychanged

Вот Испустите, что оборачивает мое виртуальное авто-свойство с уведомлением об изменениях:

MethodBuilder setMethodBuilder = typeBuilder.DefineMethod(setMethod.Name, setMethod.Attributes, setMethod.ReturnType, types.ToArray()); 
    typeBuilder.DefineMethodOverride(setMethodBuilder, setMethod); 
    ILGenerator wrapper = setMethodBuilder.GetILGenerator(); 

    ...Emit if property <> value IsModified=true here... 

    wrapper.Emit(OpCodes.Ldarg_0); 
    wrapper.Emit(OpCodes.Ldarg_1); 
    wrapper.EmitCall(OpCodes.Call, setMethod, null); 

Что мне нужно сделать, это получить набор метода из существующего «IsModified» Логического свойства и установить его, если значение свойства <> значение.

Вот пример того, что я хотел бы, чтобы излучать (это в настоящее время определяется как POCO с виртуальными автомоек свойствами):

public class AnEntity 
{ 
    string _myData; 
    public string MyData 
    { 
     get 
     { 
      return _myData; 
     } 
     set 
     { 
      if(_myData <> value) 
      { 
       IsModified = true; 
       _myData = value; 
       OnPropertyChanged("MyData");     
      } 
     } 
    } 

    bool _isModified; 
    public bool IsModified { get; set; } 
    { 
     get 
     { 
      return _isModified; 
     } 
     set 
     { 
      _isModified = value; 
      OnPropertyChanged("IsModified"); 
     } 
    } 
} 

Я застрял на этом некоторое время ... Мне удалось создать новое свойство «NewIsModified» в новом созданном прокси-классе, однако мне очень хотелось бы повторно использовать существующее свойство IsModified в моем исходном POCO.

Надеюсь, я правильно объяснил свой вопрос и его легко понять. Любая помощь будет очень признательна, и я надеюсь, что это поможет и кому-то другому.

С уважением.

+1

моно Сесил является приемлемым решением для вас? –

ответ

2

Вот рабочий код сделать это в Mono.Cecil

C# код перед:

public class AnEntityVirtual 
{ 
    public virtual string MyData { get; set; } 
    public virtual bool IsModified { get; set; } 
} 

IL код set_MyData до:

IL_0000: ldarg.0 
IL_0001: ldarg.1 
IL_0002: stfld string ClassLibrary1.AnEntityVirtual::'<MyData>k__BackingField' 
IL_0007: ret 

переписывание:

// Read the module and get the relevant type 
var assemblyPath = $"{Environment.CurrentDirectory}\\ClassLibrary1.dll"; 
var module = ModuleDefinition.ReadModule(assemblyPath); 
var type = module.Types.Single(t => t.Name == "AnEntityVirtual"); 

// Get the method to rewrite 
var myDataProperty = type.Properties.Single(prop => prop.Name == "MyData"); 
var isModifiedSetMethod = type.Properties.Single(prop => prop.Name == "IsModified").SetMethod; 
var setMethodBody = myDataProperty.SetMethod.Body; 

// Initilize before rewriting (clear pre instructions, create locals and init them) 
setMethodBody.Instructions.Clear(); 
var localDef = new VariableDefinition(module.TypeSystem.Boolean); 
setMethodBody.Variables.Add(localDef); 
setMethodBody.InitLocals = true; 

// Get fields\methos to use in the new method body 
var propBackingField = type.Fields.Single(field => field.Name == $"<{myDataProperty.Name}>k__BackingField"); 
var equalMethod = 
      myDataProperty.PropertyType.Resolve().Methods.FirstOrDefault(method => method.Name == "Equals") ?? 
      module.ImportReference(typeof(object)).Resolve().Methods.Single(method => method.Name == "Equales"); 
var equalMethodReference = module.ImportReference(equalMethod); 

// Start the rewriting 
var ilProcessor = setMethodBody.GetILProcessor(); 

// First emit a Ret instruction. This is beacause we want a label to jump if the values are equals 
ilProcessor.Emit(OpCodes.Ret); 
var ret = setMethodBody.Instructions.First(); 

ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Ldarg_0)); // load 'this' 
ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Ldfld, propBackingField)); // load backing field 
ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Ldarg_1)); // load 'value' 
ilProcessor.InsertBefore(ret, ilProcessor.Create(equalMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt, equalMethodReference)); // call equals 
ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Stloc_0)); // store result 
ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Ldloc_0)); // load result 
ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Brtrue_S, ret)); // check result and jump to Ret if are equals 
ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Ldarg_0)); // load 'this' 
ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Ldc_I4_1)); // load 1 ('true') 
ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Call, isModifiedSetMethod)); // set IsModified to 'true' 
ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Ldarg_0)); // load this 
ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Ldarg_1)); // load 'value' 
ilProcessor.InsertBefore(ret, ilProcessor.Create(OpCodes.Stfld, propBackingField)); // store 'value' in backing field 
// here you can call to Notify or whatever you want 
module.Write(assemblyPath.Replace(".dll", "_new") + ".dll"); // save the new assembly 

C# код после того, как:

public virtual string MyData 
{ 
    [CompilerGenerated] 
    get 
    { 
     return this.<MyData>k__BackingField; 
    } 
    [CompilerGenerated] 
    set 
    { 
     if (!this.<MyData>k__BackingField.Equals(value)) 
     { 
      this.IsModified = true; 
      this.<MyData>k__BackingField = value; 
     } 
    } 
} 

IL код после того, как:

IL_0000: ldarg.0 
IL_0001: ldfld string ClassLibrary1.AnEntityVirtual::'<MyData>k__BackingField' 
IL_0006: ldarg.1 
IL_0007: callvirt instance bool [mscorlib]System.String::Equals(object) 
IL_000c: stloc.0 
IL_000d: ldloc.0 
IL_000e: brtrue.s IL_001e 

IL_0010: ldarg.0 
IL_0011: ldc.i4.1 
IL_0012: call instance void ClassLibrary1.AnEntityVirtual::set_IsModified(bool) 
IL_0017: ldarg.0 
IL_0018: ldarg.1 
IL_0019: stfld string ClassLibrary1.AnEntityVirtual::'<MyData>k__BackingField' 

IL_001e: ret 

Как я уже писал, это пример того, как сделать это в Сесил. В вашем реальном коде вы можете основываться на этом, но с некоторыми изменениями.

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

Вы можете назвать OptimizeMacros.

Также, если вы точно знаете, какое свойство необходимо переписать, вы можете позвонить другому методу, например.если это string, вы можете вызвать статический метод типа строки op_Equality или op?_Inequality это является == и != из string

+0

Большое спасибо, Дуди. К сожалению, я не могу использовать Mono.Cecil, так как у нас очень ограниченные внутренние ограничения политики. Я также не знаю названия каких-либо свойств типа, за исключением isModifed до runtime, поскольку новый прокси создается на основе типов , хотя созданный тип прокси всегда будет содержать isModified. Я возьму ваш подход и попытаюсь заставить его работать с IL Emit. – Option

+0

@option Приветствую вас. Для неизвестных свойств назовите его то же самое. Просто перечислите type.Properties. Его не сложно реализовать с Reflection.Emit, тот же принцип. Если вам нужно, чтобы я сделал это, возможно, завтра утром. –

+0

благодарим вас за предложение - у меня есть эта часть, работающая там, где я перебираю свойства и добавляю к ним событие notifypropertychanged ... это только что оставленная часть isModified: public string MyData { .. . set { if (_myData <> value) { IsModified = true; _myData = значение; OnPropertyChanged («MyData»); } } } – Option

Смежные вопросы