When in doubt, write a minimal C# library that does the same thing, and see what that compiles to:
Your attempt seems to be equivalent to
using System;
static class Program {
public static DateTime? SetUtc(object value) {
return new DateTime?(DateTime.SpecifyKind(((DateTime?)value).Value, DateTimeKind.Utc));
}
};
and this compiles to:
$ mcs test.cs -target:library -optimize+ && monodis test.dll ... IL_0000: ldarg.0 IL_0001: unbox.any valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime> IL_0006: stloc.0 IL_0007: ldloca.s 0 IL_0009: call instance !0 valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime>::get_Value() IL_000e: ldc.i4.1 IL_000f: call valuetype [mscorlib]System.DateTime valuetype [mscorlib]System.DateTime::SpecifyKind(valuetype [mscorlib]System.DateTime, valuetype [mscorlib]System.DateTimeKind) IL_0014: newobj instance void valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime>::'.ctor'(!0) IL_0019: ret ...
Thee first difference with your version is that ldarg
is used instead of ldarga
. You want unbox.any
to check the passed value, not a pointer to the passed value. (ldarga
does work too in my tests, but ldarg
makes more sense anyway.)
The second, and more relevant, difference with your version is that after unbox.any
, the value is stored, and then a reference to that location is loaded. This is because the implicit this
parameter of instance methods of value types have type ref T
, rather than the T
you're used to for instance methods of reference types. If I do include that stloc.0
/ldloca.s 0
, your code then passes its test, on my system.
However, as you unconditionally read the Value
property after casting to DateTime?
, you might as well cast straight to DateTime
and avoid the problem entirely. The only difference would be which exception you get when a value of the wrong type is passed in.
If you instead want something like
public static DateTime? SetUtc(object value) {
var local = value as DateTime?;
return local == null ? default(DateTime?) : DateTime.SpecifyKind(local.Value, DateTimeKind.Utc);
}
then I would use something like
var label1 = il.DefineLabel();
var label2 = il.DefineLabel();
il.Emit(OpCodes.Ldarg_S, 0); // object
il.Emit(OpCodes.Isinst, typeof(DateTime)); // boxed DateTime
il.Emit(OpCodes.Dup); // boxed DateTime, boxed DateTime
il.Emit(OpCodes.Brfalse_S, label1); // boxed DateTime
il.Emit(OpCodes.Unbox_Any, typeof(DateTime)); // unboxed DateTime
il.Emit(OpCodes.Ldc_I4_1); // unboxed DateTime, int
il.Emit(OpCodes.Call, typeof(DateTime).GetMethod("SpecifyKind")); // unboxed DateTime
il.Emit(OpCodes.Newobj, typeof(DateTime?).GetConstructor(new[] { typeof(DateTime) })); // unboxed DateTime?
il.Emit(OpCodes.Br_S, label2);
il.MarkLabel(label1); // boxed DateTime (known to be null)
il.Emit(OpCodes.Unbox_Any, typeof(DateTime?)); // unboxed DateTime?
il.MarkLabel(label2); // unboxed DateTime?
il.Emit(OpCodes.Ret);