2016-11-15 2 views
0

J'implémente un gestionnaire propertychanged émis pour mon objet POCO contenant des auto-propriétés virtuelles, et j'ai du code qui fonctionne au point où propertychanged est levé chaque fois que je change la propriété sous-jacente. La raison pour cela est que je partage un objet POCO avec le serveur (pour le meilleur ou pour le pire), où je vais envoyer des objets modifiés au serveur. Je ne peux pas décorer l'objet POCO avec des attributs (puisque le serveur aurait également ces décorateurs, car nous partageons la classe commune) et je ne peux pas utiliser des outils tiers tels que Fody ou PostSharp en raison de politiques. J'ai besoin de savoir si l'objet a été modifié, et je suis coincé là-dessus.IL Emit - définit une propriété existante avec une valeur booléenne avant notifypropertychanged

Voici le Emit qui enveloppe mes auto-propriétés virtuelles avec notification de modification:

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

Ce que je dois faire est d'obtenir la définir la méthode de l'existant « IsModified » propriété booléenne et définissez si la valeur de la propriété <> valeur.

Voici un exemple de ce que je voudrais émettre (ce qui est actuellement définie comme POCO avec auto-propriétés virtuelles):

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

Je suis coincé sur ce pendant un certain temps ... J'ai réussi à créer une nouvelle propriété appelée "NewIsModified" dans la nouvelle classe proxy qui a été créée, mais j'aimerais beaucoup réutiliser la propriété IsModified existante dans mon POCO d'origine. J'espère avoir bien expliqué ma question et que je peux la comprendre facilement. Toute aide serait grandement appréciée, et j'espère que cela aidera aussi quelqu'un d'autre.

Cordialement.

+1

mono est cecil une solution acceptable pour vous? –

Répondre

2

Voici un code de travail pour faire en Mono.Cecil

code C# avant:

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

code IL de la set_MyData avant:

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

La ré-écriture:

// 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 

code C# après:

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

code IL après:

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 

Comme je l'ai écrit, ceci est un exemple de la façon de le faire dans Cecil. Dans votre code réel, vous pouvez vous baser sur cela, mais avec quelques changements. Par exemple, vous pouvez créer un champ privé pour votre propriété et ne pas utiliser le champ de sauvegarde généré par le compilateur.

Vous pouvez appeler OptimizeMacros.

De même, si vous savez exactement quelle propriété vous devez réécrire, vous pouvez appeler une autre méthode équivalente, par ex.si elle est string, vous pouvez appeler une méthode statique de type chaîne op_Equality ou op?_Inequality c'est le == et != de string

+0

Merci beaucoup, Dudi. Malheureusement, je ne peux pas utiliser Mono.Cecil, car nous avons des contraintes de politique interne très limitées. Je ne connais pas non plus le nom des propriétés d'un type, à l'exception de isModifed jusqu'à l'exécution, car un nouveau proxy est créé sur la base des types , bien que le type de proxy créé contienne toujours isModified. Je vais suivre votre approche et essayer de la faire fonctionner avec IL Emit. – Option

+0

@option Bienvenue. Pour les propriétés inconnues, nommez le même. Juste énumérer type.Properties. Ce n'est pas difficile à mettre en œuvre avec Reflection.Emit, même principe. Si vous avez besoin de moi pour le faire, je le ferais peut-être demain matin. –

+0

merci beaucoup pour l'offre - j'ai cette partie tout travail où je suis en boucle les propriétés et en leur ajoutant l'événement notifypropertychanged ... c'est juste la partie isModified à faire: chaîne publique MyData { .. set { if (valeur _myData <>) { IsModified = true; _myData = valeur; OnPropertyChanged ("MyData"); }} } – Option