2016-11-15 60 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

相關問題