I'm making a class for generating wrappers in runtime, that get a class like this:
class Test
{
public int A { get; set; }
public int B { get; set; }
public string C { get; set; }
public DateTimeOffset D { get; set; }
public bool E { get; set; }
}
and creates a wrapper type like
class newTest
{
private Test _inner;
public newTest(Test inner)
{
_inner = inner;
}
public int A { get => _inner.A; }
public int B { get => _inner.B; }
public string C
{
get => _inner.C;
}
public DateTime D
{
get => _inner.D.UtcDateTime;
}
public short E
{
get => (short)(_inner.E ? 1 : 0);
}
}
and all works fine, except getter for property D (only the DateTime part in utc timezone is needed). Code for adding property for DateTimeOffset is
private static void AddDateTimeOffsetProperty(TypeBuilder typeBuilder, FieldInfo fieldBuilder,
PropertyInfo field)
{
var propBuilder = typeBuilder.DefineProperty(field.Name
, PropertyAttributes.HasDefault
, typeof(DateTime)
, null);
var getBuilder = typeBuilder.DefineMethod($"get_{field.Name}",
_getAttr,
typeof(DateTime),
Type.EmptyTypes);
ILGenerator ilGen = getBuilder.GetILGenerator();
// load args to memory
ilGen.Emit(OpCodes.Ldarg_0);
// load required field from "_inner" obj
ilGen.Emit(OpCodes.Ldfld, fieldBuilder);
// call _inner getter
ilGen.EmitCall(OpCodes.Callvirt, field.GetMethod!, Array.Empty<Type>());
// alloc local variable
ilGen.Emit(OpCodes.Stloc_0);
ilGen.Emit(OpCodes.Ldloca_S);
MethodInfo utcDateTimeMethod = typeof(DateTimeOffset).GetProperty("UtcDateTime").GetMethod;
// get UtcDateTime
ilGen.EmitCall(OpCodes.Call, utcDateTimeMethod!, Array.Empty<Type>());
// return
ilGen.Emit(OpCodes.Ret);
propBuilder.SetGetMethod(getBuilder);
}
Here "field" is the property of inner class. After class was generated, create instance of Test class and wrap it
var testObj = new Test
{
A = 7,
B = 6,
C = "World",
E = false,
D = DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromHours(7)),
F = 14
};
var wrapper = Activator.CreateInstance(wrapperTypeForTest, testObj);
after that wrapper.D
will return "Common Language Runtime detected an invalid program.", other properties work.
CodePudding user response:
You're storing something in slot 0 (with Stloc_0
), but you never declared slot 0.
You need to declare a local first, so ILGenerator
can actually create the slot to store your variable in.
LocalBuilder local = ilGen.DeclareLocal(typeof(DateTimeOffset));
And then:
ilGen.Emit(OpCodes.Stloc, local);
You're using Ldloca_S
to load the address of a local slot, but you never pass the slot to actually load.
ilGen.Emit(OpCodes.Ldloca, local);
(ILGenerator
will turn this into Ldloca_S
if appropriate).
From the docs for ILGenerator.EmitCall
:
Puts a call or callvirt instruction onto the Microsoft intermediate language (MSIL) stream to call a varargs method.
You're not calling a varargs method. Varargs methods are few and far between. You're calling a normal method, so just:
ilGen.Emit(OpCodes.Callvirt, field.GetMethod!);
and:
ilGen.Emit(OpCodes.Call, utcDateTimeMethod!);