Search code examples
c#cilil

DateTime object not returning correct values from MSIL


I noticed some code I have written in MSIL to get arbitrary properties of objects at high speed is not getting the correct values of DateTime properties. It's always returning the same values no matter the actual value of the DateTime object, ex. Year always returns 1, Millisecond returns 88, etc...

Some stripped down code in LINQPad that demonstrates this. Getting mc.Inner.Age returns the correct value, mc.Inner.DateOfBirth returns the proper DateTime value, but trying to get any specific part of mc.Inner.DateOfBirth always returns an incorrect value. I've looked at and tried a few things to get it to work, but I'm not experienced enough to really know what else to try at this point. I'm not sure if there is something subtly wrong in my code or there is something special about the DateTime object that causes this to happen.

void Main()
{
    var mc = new MyClass();
    mc.FirstName = "Jane";
    mc.LastName = "Doe";
    mc.Inner.DateOfBirth = new DateTime(1960, 2, 13);
    mc.Inner.Age = 54;

    Object obj = mc;    
    obj = this.GetObjectProperty(obj, "Inner");
    obj = this.GetObjectProperty(obj, "DateOfBirth");
    obj = this.GetObjectProperty(obj, "Year");
    obj.Dump();

    obj = mc;
    obj = obj.GetType().GetProperty("Inner").GetValue(obj);
    obj = obj.GetType().GetProperty("DateOfBirth").GetValue(obj);
    obj = obj.GetType().GetProperty("Year").GetValue(obj);
    obj.Dump();
}

private Object GetObjectProperty(Object obj, String property)
{
    var m = obj.GetType().GetMethod("get_" + property, BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty);

    DynamicMethod meth = new DynamicMethod("GetObjectProperty", typeof(Object), new [] { typeof(Object) }, obj.GetType());
    var il = meth.GetILGenerator();

    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Castclass, obj.GetType());
    il.EmitCall(OpCodes.Call, m, null);

    if (m.ReturnType.IsValueType)
        il.Emit(OpCodes.Box, m.ReturnType);

    il.Emit(OpCodes.Ret);

    return ((GetObjectPropertyDelegate)meth.CreateDelegate(typeof(GetObjectPropertyDelegate), obj))();
}

private delegate Object GetObjectPropertyDelegate();

public class MyClass
{
    public MyClass()
    {
        this.Inner = new MyInnerClass();
    }

    public String FirstName { get; set; }
    public String LastName { get; set; }
    public MyInnerClass Inner { get; set; }
}

public class MyInnerClass
{
    public DateTime DateOfBirth { get; set; }
    public int Age { get; set; }
}

Solution

  • Calls to value types must pass a managed pointer as the this argument. This is to support mutating methods on value types.

    You are passing an object reference to a boxed instance of a value type. Probably, the jitted code of DateTime.Year is accessing some random bits of memory. You have broken memory safety. This IL is not verifiable. I don't recal the boxed and unboxed memory layout but you might be reading (parts of) the method table or the vtable or some other part of the object header.

    Use Unbox to obtain a managed pointer to the contents of the boxed instance.