2016-11-15 2 views
0

Ich implementiere einen emmitting proposechanged-Handler für mein POCO-Objekt mit virtuellen Auto-Eigenschaften, und ich habe Code, der an dem Punkt funktioniert, an dem propertychange ausgelöst wird wenn ich die zugrunde liegende Eigenschaft ändere. Der Grund dafür ist, dass ich ein POCO-Objekt mit dem Server teile (zum Guten oder zum Schlechten), wo ich modifizierte Objekte an den Server senden werde. Ich kann das POCO-Objekt nicht mit Attributen dekorieren (da der Server auch diese Dekoratoren hätte, da wir die gemeinsame Klasse teilen) und ich kann aufgrund von Richtlinien keine Tools von Drittanbietern wie Fody oder PostSharp verwenden. Ich muss verfolgen, ob das Objekt geändert wurde, und ich stecke darauf fest.IL Emit - setze eine existierende Eigenschaft mit einem booleschen Wert vor notifypropertychanged

Hier ist die Emit, dass meine virtuelle Auto-Eigenschaften mit Änderungsbenachrichtigung Wraps:

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); 

Was muss ich die Methode des bestehenden gesetzt tun bekommen ist „IsModified“ boolean Eigenschaft und legen Sie es, wenn der Eigenschaftswert <> Wert.

Hier ist ein Beispiel dafür, was Ich mag würde emittieren (dies derzeit als POCO mit virtuellen Auto-Eigenschaften definiert ist):

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"); 
     } 
    } 
} 

Ich habe auf diesem für eine Weile stecken geblieben ... Ich habe es geschafft, eine neue Eigenschaft namens "NewIsModified" in der neuen Proxy-Klasse zu erstellen, die erstellt wurde, jedoch möchte ich die vorhandene IsModified-Eigenschaft in meinem ursprünglichen POCO sehr gerne wiederverwenden.

Ich hoffe, ich habe meine Frage richtig erklärt und ist leicht zu verstehen. Jede Hilfe würde sehr geschätzt werden, und ich hoffe, dass es auch anderen helfen wird.

Mit freundlichen Grüßen.

+1

mono cecil ist eine akzeptable Lösung für Sie? –

Antwort

2

ist hier ein Arbeits Code dafür

C# -Code vor in Mono.Cecil tun:

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

IL-Code des set_MyData vor:

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

das Umschreiben:

// 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# -Code nach:

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-Code nach:

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 

Wie ich schrieb, ist dies ein Beispiel dafür, wie es in Cecil zu tun. In Ihrem echten Code können Sie darauf basieren, aber mit einigen Änderungen.

Sie können beispielsweise ein privates Feld für Ihre Eigenschaft erstellen und nicht das vom Compiler generierte Hintergrundfeld verwenden.

Sie können OptimizeMacros aufrufen.

Auch wenn Sie genau wissen, welche Eigenschaft Sie neu schreiben müssen, können Sie zu anderen gleichen Methode, z.wenn es string ist, können Sie eine statische Methode vom Typ String nennen op_Equality oder op?_Inequality ist dies die == und != von string

+0

Vielen Dank, Dudi. Leider kann ich Mono.Cecil nicht verwenden, da die internen Richtlinienbeschränkungen sehr begrenzt sind. Ich kenne auch nicht den Namen irgendwelcher Eigenschaften eines Typs außer isModifed bis zur Laufzeit, da ein neuer Proxy basierend auf den Typen erstellt wird, obwohl der erstellte Proxy-Typ immer isModified enthält. Ich werde deinen Ansatz verfolgen und versuchen, es mit IL Emit funktionieren zu lassen. – Option

+0

@option Willkommen. Bei unbekannten Eigenschaften ist der Name gleich. Geben Sie nur type.Properties ein. Es ist nicht schwer, es mit Reflection.Emit, das gleiche Prinzip zu implementieren. Wenn du mich brauchst, könnte ich es vielleicht morgen früh tun. –

+0

Vielen Dank für Ihr Angebot - Ich habe diesen Teil alle arbeiten, wo ich die Eigenschaften durchlaufen und das notifypropertychanged-Ereignis zu ihnen hinzufügen ... es ist nur der isModified Teil noch zu tun: öffentliche Zeichenfolge MyData { .. { if (_myData <> Wert) { IsModified = true; _myData = Wert; OnPropertyChanged ("MyData"); } } } – Option

Verwandte Themen