C# Emit, как написать оператор if
Во -первых, у меня есть класс
internal class Parent
{
protected void Write(string message, [CallerMemberName] string caller = null)
{
Console.WriteLine($"{caller} :: {message}");
}
}
и я хочу динамически создать класс, класс имеет свойство "Имя", если значение свойства изменилось, то вызов метода записи, выглядит
class Child : Parent
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value) { Write("changedto: " + value); _name = value; }
}
}
}
то, что я хочу знать, является основным моментом. как написать это в Emit. пожалуйста помоги.
я хочу немного кода ниже
private static MethodBuilder BuildSetter(TypeBuilder typeBuilder, PropertyInfo property, FieldBuilder fieldBuilder, MethodAttributes attributes)
{
var setterBuilder = typeBuilder.DefineMethod($"set_{property.Name}", attributes, null, new Type[] { property.PropertyType });
var ilGenerator = setterBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0); //this
ilGenerator.Emit(OpCodes.Ldarg_1); // the first one in arguments list
//code should be here
ilGenerator.Emit(OpCodes.Stfld, fieldBuilder);
ilGenerator.Emit(OpCodes.Ret);
return setterBuilder;
}
обновленный
var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public,typeof(PropertyChanged));
var t = typeof(PropertyChanged);
var m2 = t.GetMethod("ValueChanged");
вы видите, когда я создаю тип, я использую родительский тип, а в родительском типе есть метод с именем "ValueChanged"
protected void ValueChanged(object value,[CallerMemberName] string property = null)
я хочу вызвать его в методе набора.
обновление 2
private static MethodBuilder BuildSetter(TypeBuilder typeBuilder, PropertyInfo property, FieldBuilder fieldBuilder, MethodAttributes attributes)
{
var propertyType = property.PropertyType;
var setterBuilder = typeBuilder.DefineMethod($"set_{property.Name}", attributes, null, new Type[] { propertyType });
var setIl = setterBuilder.GetILGenerator();
Label exitSet = setIl.DefineLabel(); // define label to jump in case condition is false
setIl.Emit(OpCodes.Ldarg_0); // this
setIl.Emit(OpCodes.Ldfld, fieldBuilder); // _name field
setIl.Emit(OpCodes.Ldarg_1); // value
var inequality = propertyType.GetMethod("Equals", new[] { propertyType});
setIl.Emit(OpCodes.Callvirt, inequality); // '!=' method
setIl.Emit(OpCodes.Brtrue_S, exitSet); // check for inequality
setIl.Emit(OpCodes.Ldarg_0); // load string literal
setIl.Emit(OpCodes.Ldarg_1); // value
setIl.Emit(OpCodes.Ldstr, property.Name);
var m = typeBuilder.BaseType.GetMethod("ValueChanged", new Type[] {typeof(object),typeof(string) });
setIl.Emit(OpCodes.Call, m);
setIl.Emit(OpCodes.Ldarg_0); // this
setIl.Emit(OpCodes.Ldarg_1); // value
setIl.Emit(OpCodes.Stfld, fieldBuilder); // save the new value into _name
setIl.MarkLabel(exitSet); // mark the label
setIl.Emit(OpCodes.Ret); // return
return setterBuilder;
}
обновить 3 снимка экрана
обновление 4
возможно, я нашел причину ошибки, см. Отражение излучения и наследование типов: вызов конструкторов базового типа
обновление 5
наконец, я получил причину ошибки. это было не то, о чем я догадывался в обновлении 4, это было вызвано вызовом родительского метода "ValueChanged" . перед тем, как передать параметр в метод, мы должны пометить его как Object, если исходный тип данных IsValueType. см. ссылку ниже,
2 ответа
Ответ @AlbertK показывает правильный путь, я добавлю полный код. Надеюсь, это поможет.
Я положил все в один метод.. рефакторинг, как вы хотите.
// define assembly and module
var propertyName = "Name";
var propertyType = typeof(string);
var ab = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName("dynamicAssembly"),
AssemblyBuilderAccess.Save);
var mb = ab.DefineDynamicModule("dynamicModule", "dynamicModule.dll");
// define type, field and property
var tb = mb.DefineType("dynamicType");
var fb = tb.DefineField("_name", propertyType, FieldAttributes.Private);
var pb = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, Type.EmptyTypes);
var get = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
var set = tb.DefineMethod("set_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new[] { propertyType });
// write the IL for the get method
var getIl = get.GetILGenerator();
getIl.Emit(OpCodes.Ldarg_0); // this
getIl.Emit(OpCodes.Ldfld, fb); // _name field
getIl.Emit(OpCodes.Ret);
// write the IL for the set method
var setIl = set.GetILGenerator();
Label exitSet = setIl.DefineLabel(); // define label to jump in case condition is false
setIl.Emit(OpCodes.Ldarg_0); // this
setIl.Emit(OpCodes.Ldfld, fb); // _name field
setIl.Emit(OpCodes.Ldarg_1); // value
var inequality = propertyType.GetMethod("op_Inequality", new[] { propertyType, propertyType });
setIl.Emit(OpCodes.Call, inequality); // '!=' method
setIl.Emit(OpCodes.Brfalse_S, exitSet); // check for inequality
setIl.Emit(OpCodes.Ldstr, "changedto:"); // load string literal
setIl.Emit(OpCodes.Ldarg_1); // value
var concat = propertyType.GetMethod("Concat", new[] { propertyType, propertyType });
setIl.Emit(OpCodes.Call, concat); // concat two strings (literal + value)
var writeline = typeof(Console).GetMethod("WriteLine", new[] { propertyType });
setIl.Emit(OpCodes.Call, writeline); // write
setIl.Emit(OpCodes.Ldarg_0); // this
setIl.Emit(OpCodes.Ldarg_1); // value
setIl.Emit(OpCodes.Stfld, fb); // save the new value into _name
setIl.MarkLabel(exitSet); // mark the label
setIl.Emit(OpCodes.Ret); // return
pb.SetGetMethod(get);
pb.SetSetMethod(set);
tb.CreateType(); // complete the type
ab.Save("dynamicModule.dll"); // save the assembly to disk
И результат
internal class dynamicType
{
private string _name;
public string Name
{
get
{
return this._name;
}
set
{
if (this._name != value)
{
Console.WriteLine("changedto:" + value);
this._name = value;
}
}
}
}
Вы можете использовать метку, затем вы можете перейти к ней с помощью OpCodes.Beq
это проверяет равенство двух переменных в стеке. Попробуй это:
...
var toEnd = ilGenerator.DefineLabel();
ilGenerator.Emit(OpCodes.Beq, toEnd);
ilGenerator.Emit(OpCodes.Ldstr, "Changed");
ilGenerator.Emit(OpCodes.Call,
typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));
ilGenerator.Emit(OpCodes.Stfld, fieldBuilder);
ilGenerator.MarkLabel(toEnd);
ilGenerator.Emit(OpCodes.Ret);
Это должно быть эквивалентно:
if (_name != value)
{
Console.WriteLine("Changed");
_name = value;
}
Примечание: перед выпуском OpCodes.Beq
вам нужно вставить в стек два элемента (value
а также _name
поле).
Более подробный код вы можете увидеть в ответе Дуди Келети. Он использует op_Inequality
вместо OpCodes.Beq
перейти к метке цели.