I'm trying to write a program that creates a dynamic "Wrapper" class around an existing class with properties, and redirects all the virtual properties getters and setters to a dedicated GetValue
and SetValue
methods in BaseClass
. It's a bit difficult to explain, so here's the code so far:
public abstract class BaseClass
{
protected void SetValue(string propertyName, object value) { ... }
protected object? GetValue(string propertyName) { ... }
}
public class DataClass : BaseClass
{
public virtual string? StrValue { get; set; }
public virtual int IntValue { get; set; }
public virtual bool BoolValue { get; set; }
public virtual List<float>? FloatListValue { get; set; }
}
In this example, my code generates a new class called DataClassWrapper
that inherits from DataClass
, and overrides all the properties' getter and setter methods with IL code that redirects the call to re respective GetValue and SetValue methods in the base class, passing the property's name as the first argument. The actual method that does all this is a bit too long for this post, but it can be viewed here.
Here's the code that generates the setter for each property: (works correctly in all cases)
setMethodGenerator.Emit(OpCodes.Ldarg_0); // 'this'
setMethodGenerator.Emit(OpCodes.Ldstr, propertyInfo.Name); // 1st argument of SetValue
setMethodGenerator.Emit(OpCodes.Ldarg_1); // value passed into this setter, aka. `value`
setMethodGenerator.Emit(OpCodes.Box, propertyInfo.PropertyType); // cast it to `object`
setMethodGenerator.EmitCall(OpCodes.Call, baseSetterMethod, Type.EmptyTypes); // call SetValue
setMethodGenerator.Emit(OpCodes.Ret); // return
where propertyInfo
describes a property from DataClass
, and baseSetterMethod
references the SetValue()
method from the base class.
Getter:
getMethodGenerator.Emit(OpCodes.Ldarg_0); // 'this'
getMethodGenerator.Emit(OpCodes.Ldstr, propertyInfo.Name); // 1st (and only) argument of GetValue
getMethodGenerator.EmitCall(OpCodes.Call, baseGetterMethod, Type.EmptyTypes); // call GetValue
getMethodGenerator.Emit(OpCodes.Castclass, propertyInfo.PropertyType); // cast result to expected type
getMethodGenerator.Emit(OpCodes.Ret); // return it
And some example usage:
var type = CreateDynamicType(typeof(DataClass), baseGetterMethod, baseSetterMethod);
var inst = (DataClass) Activator.CreateInstance(type)!;
inst.IntValue = 1123;
Console.Out.WriteLine(inst.IntValue);
I can read and write the properties StrValue
and FloatListValue
without any issues, my custom GetValue
and SetValue
methods are being called as they should be.
However if I try to read a primitive property like BoolValue
or IntValue
, it returns a seemingly random garbage number (for example -1151033224 the last time I ran this code). If I convert my IntValue
into a nullable IntValue?
value, it still returns gibberish, but this time the random numbers are much smaller (in the 0-700 range).
The setter method does receive the original int value correctly, and the getter also retrieves it intact, so the problem must be around the generated IL code that calls the getter. But it only seems to happen with primitive types. Does anyone have an idea?
CodePudding user response:
I think your problem is the castclass
instruction. The documentation says:
typeTok [the argument] is a metadata token (a typeref, typedef or typespec), indicating the desired class. If typeTok is a non-nullable value type or a generic parameter type it is interpreted as “boxed” typeTok. If typeTok is a nullable type, Nullable, it is interpreted as “boxed” T.
Unlike coercions (§III.1.6) and conversions (§III.3.27), a cast never changes the actual type of an object and preserves object identity (see Partition I)
That means that if the object is a boxed instance of a value type, castclass does not unbox it but retains the object reference. So this will actually return the managed(!) address of the object, which is useless. In case of a value type, you need to use an unbox.any
instruction.