IL Emit - 在 notify属性changed 之前用布尔值设置现有的 属性
IL Emit - set an existing property with a boolean value before notifypropertychanged
我正在为我的包含虚拟自动属性的 POCO 对象实现一个已发出的 属性changed 处理程序,并且我的代码可以在任何时候引发 属性changed我更改了基础 属性。这样做的原因是我正在与服务器共享一个 POCO 对象(无论好坏),我将在其中将修改后的对象发送到服务器。我不能用属性装饰 POCO 对象(因为服务器也会有这些装饰器,因为我们共享公共 class),而且由于政策原因,我不能使用第三方工具,如 Fody 或 PostSharp。需要跟踪对象是否被修改,一直卡在这个
这是使用更改通知包装我的虚拟自动属性的 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" 布尔值 属性 的 set 方法,并在 属性 值时设置它<> 值。
这是我想要发出的示例(目前定义为具有虚拟自动属性的 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");
}
}
}
我已经坚持了一段时间...我设法在创建的新代理 class 中创建了一个名为 "NewIsModified" 的新 属性,但是,我非常想在我原来的 POCO 中重用现有的 IsModified 属性。
我希望我已经正确解释了我的问题并且易于理解。任何帮助将不胜感激,我希望它也能帮助其他人。
亲切的问候。
这是在 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
如我所写,这是一个如何在 Cecil 中执行此操作的示例。
在您的真实代码中,您可以以此为基础,但进行一些更改。
例如,您可以为 属性 创建私有字段,而不使用编译器生成的支持字段。
您可以调用OptimizeMacros。
此外,如果您确切地知道需要重写哪个 属性,您可以调用其他相同的方法,例如如果是string
,你可以调用字符串op_Equality
或op?_Inequality
类型的静态方法这是string
[=的==
和!=
22=]
我正在为我的包含虚拟自动属性的 POCO 对象实现一个已发出的 属性changed 处理程序,并且我的代码可以在任何时候引发 属性changed我更改了基础 属性。这样做的原因是我正在与服务器共享一个 POCO 对象(无论好坏),我将在其中将修改后的对象发送到服务器。我不能用属性装饰 POCO 对象(因为服务器也会有这些装饰器,因为我们共享公共 class),而且由于政策原因,我不能使用第三方工具,如 Fody 或 PostSharp。需要跟踪对象是否被修改,一直卡在这个
这是使用更改通知包装我的虚拟自动属性的 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" 布尔值 属性 的 set 方法,并在 属性 值时设置它<> 值。
这是我想要发出的示例(目前定义为具有虚拟自动属性的 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");
}
}
}
我已经坚持了一段时间...我设法在创建的新代理 class 中创建了一个名为 "NewIsModified" 的新 属性,但是,我非常想在我原来的 POCO 中重用现有的 IsModified 属性。
我希望我已经正确解释了我的问题并且易于理解。任何帮助将不胜感激,我希望它也能帮助其他人。
亲切的问候。
这是在 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
如我所写,这是一个如何在 Cecil 中执行此操作的示例。 在您的真实代码中,您可以以此为基础,但进行一些更改。
例如,您可以为 属性 创建私有字段,而不使用编译器生成的支持字段。
您可以调用OptimizeMacros。
此外,如果您确切地知道需要重写哪个 属性,您可以调用其他相同的方法,例如如果是string
,你可以调用字符串op_Equality
或op?_Inequality
类型的静态方法这是string
[=的==
和!=
22=]