2016-11-15 61 views
0

我为包含虚拟自动属性的POCO对象实现了一个发射的属性changed处理程序,并且我得到的代码可以在propertychanged被提出的地方工作每当我改变基础财产。这样做的原因是我与服务器共享一个POCO对象(好或坏),我将发送修改对象到服务器。我无法用属性装饰POCO对象(因为服务器也会有这些装饰器,因为我们共享公共类),并且由于策略原因,我无法使用Fody或PostSharp等第三方工具。我需要跟踪对象是否被修改,并且我坚持这一点。IL Emit - 在notifypropertychanged之前用布尔值设置一个现有的属性

这里是一个封装与变更通知我的虚拟自动属性的Emit:

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”的新属性,但是,我非常想在原始POCO中重新使用现有的IsModified属性。

我希望我已经正确地解释了我的问题,并且很容易理解。任何帮助将不胜感激,我希望它也能帮助别人。

亲切的问候。

+1

单丝丝的!=是你的一个可接受的解决方案? –

回答

2

这里是工作的代码做在Mono.Cecil能

C#代码之前:

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

IL代码:

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_Equalityop?_Inequality的静态方法,这是==string

+0

非常感谢Dudi。不幸的是,我不能使用Mono.Cecil,因为我们的内部政策限制非常有限。我也不知道类型的任何属性的名称,除了isModifed直到运行时,因为根据类型创建了新的代理,尽管创建的代理类型将始终包含isModified。我会采取你的方法,并尝试使其与IL Emit合作。 – Option

+0

@option您好!对于未知的属性命名相同。只需枚举type.Properties。使用Reflection.Emit同样的原则来实现它并不困难。如果你需要我这样做,也许我明天早上可以。 –

+0

非常感谢你提供 - 我有这部分工作,我正在循环通过属性和添加notifypropertychanged事件给他们...这只是isModified部分要做: 公共字符串MyData { .. set if(_myData <> value) { IsModified = true; _myData = value; OnPropertyChanged(“MyData”); } } } – Option

相关问题