Ilgenerator:将默认初始化的System.Nullable推送到Stack

时间:2017-03-10 11:24:59

标签: c# cil

我目前正在尝试编写代码,用户可以使用默认值初始化类的选定属性(用作SQL到C#映射项目的一部分)。 以下代码给出了VerificationException,它可能会破坏运行时的稳定性:

private static void InitWithNullable(ILGenerator il, MethodInfo setter, Type objType)
{
    var local = il.DeclareLocal(objType);

    il.Emit(OpCodes.Ldloca, local);
    il.Emit(OpCodes.Initobj, local);
    il.Emit(OpCodes.Ldloca, local);
    il.Emit(OpCodes.Ldobj, objType);

    il.EmitCall(OpCodes.Callvirt, setter, null);
}

其中一个属性属于System.Nullable<MyEnum>类型。我有什么想法吗?

2 个答案:

答案 0 :(得分:0)

InitObj应为:

il.Emit(OpCodes.Initobj, objType);

来自MSDN

  

以下Emit方法重载可以使用initobj操作码:

     

ILGenerator.Emit(OpCode,Type)

il.Emit(OpCodes.Ldobj, objType);可能是错的......我需要查看整个生成的代码。你需要在某处加载 this (即ldarg.0} ......

大概:

var local = il.DeclareLocal(objType);

il.Emit(OpCodes.Ldarg, 0); // Used by the OpCodes.callvirt

il.Emit(OpCodes.Ldloca, local);
il.Emit(OpCodes.Initobj, objType);

il.Emit(OpCodes.Ldloc, local.Index);
il.EmitCall(OpCodes.Callvirt, setter, null);

TryRoslyn反编译样本一样。

答案 1 :(得分:0)

以下是在返回DBNULL时将单个属性设置为加载值或默认值的工作示例

    public void MiniTest()
    {
        IDbConnection con = Session.Connection;
        var method = new DynamicMethod("test", null,
            new Type[] { typeof(IDataReader), typeof(MyOrder) });
        var il = method.GetILGenerator();
        var defaultLabel = il.DefineLabel();
        var endLabel = il.DefineLabel();
        // generate isdbnull check
        il.Emit(OpCodes.Ldarg_0); //push reader
        il.Emit(OpCodes.Ldc_I4, 0); //push idx on stack
        il.EmitCall(OpCodes.Callvirt, typeof(IDataRecord).GetMethod("IsDBNull"), null);
        il.Emit(OpCodes.Brtrue_S, defaultLabel);
        //stack now empty
        //do the read call and assign the value
        il.Emit(OpCodes.Ldarg_1); //push data
        il.Emit(OpCodes.Ldarg_0); //push reader
        il.Emit(OpCodes.Ldc_I4, 0); //push idx on stack
        il.EmitCall(OpCodes.Callvirt, typeof(IDataRecord).GetMethod("GetDateTime"), null); // call r.Get...(idx) and push result on stack
        //stack is data, DateTime
        var ctor = typeof(DateTime?).GetConstructor(new[] { typeof(DateTime) });
        il.Emit(OpCodes.Newobj, ctor); // create the system.nullable and push it on stack
        // stack is data, DateTime?
        var prop = LinqExt.GetPropertyFast<MyOrder, DateTime?>(x => x.Shipment);
        il.EmitCall(OpCodes.Callvirt, prop.GetSetMethod(), null); // call d.Property setter
        //consumes data + Datetime? value
        il.Emit(OpCodes.Br, endLabel);
        il.MarkLabel(defaultLabel);
        //we start with an empty stack here
        il.Emit(OpCodes.Ldarg_1); //push data
        var local = il.DeclareLocal(typeof(DateTime?));
        il.Emit(OpCodes.Ldloca, local);
        il.Emit(OpCodes.Initobj, typeof(DateTime?));
        il.Emit(OpCodes.Ldloc, local.LocalIndex);
        //stack is now data, new DateTime?()
        il.EmitCall(OpCodes.Callvirt, prop.GetSetMethod(), null);
        //stack empty
        il.MarkLabel(endLabel);
        il.Emit(OpCodes.Ret);
        var action = (Action<IDataReader, MyOrder>)method.CreateDelegate(typeof(Action<IDataReader, MyOrder>));
        List<MyOrder> data = new List<MyOrder>();
        using (var reader = con.Query(
@"Select o.Shipment
 from TestOrder o
 where o.Order_Id = :id",
            0,
            new { id = 1 }))
        {
            while(reader.Read())
            {
                MyOrder u = new MyOrder();
                action(reader, u);
                data.Add(u);
            }
        }

        Assert.AreEqual(1, data.Count);
        Assert.IsNull( data[0].Shipment);
    }

这应该转化为以下行动:

Action<IDataReader, MyOrder> action = (r, o) =>
        {
            if (r.IsDBNull(0))
            {
                o.Shipment = null;
            }
            else
            {
                o.Shipment = r.GetDateTime(0);
            }
        };

进一步简化为System.Nullable只包含null:

    [TestMethod]
    public void MicroTest2()
    {
        var action = InitWithNull<ExtUser, int?>(x => x.NIntId, "MicroTest2Impl");

        ExtUser u = new ExtUser();
        u.NIntId = 1;
        action( u);

        Assert.IsNull(u.NIntId);
    }

    private static Action<TObject> InitWithNull<TObject, TProperty>(
        Expression<Func<TObject,TProperty>> property, string name)
    {

        var method = new DynamicMethod(name, null,
            new Type[] { typeof(TObject) });

        var il = method.GetILGenerator();

        var prop = LinqExt.GetPropertyFast(property);
        //we start with an empty stack here

        var local = il.DeclareLocal(typeof(TProperty));
        il.Emit(OpCodes.Ldarg_0); //push data
        il.Emit(OpCodes.Ldloca, local);
        il.Emit(OpCodes.Initobj, typeof(TProperty));

        il.Emit(OpCodes.Ldloc, local.LocalIndex);
        //stack is now data, new TProperty?()
        il.EmitCall(OpCodes.Callvirt, prop.GetSetMethod(), null);
        //stack empty
        il.Emit(OpCodes.Ret);
        var action = (Action<TObject>)method.CreateDelegate(typeof(Action<TObject>));
        return action;
    }