1

我正在编写自己的序列化程序,它发出 IL 来生成 [反] 序列化代码。

对于 nullables,我想我可以生成以下内容(int?作为 ex)(假设我们已经生成了 [de]serialize 的方法int):

public static void Serialize(Stream stream, int? value, object context)
{
    Serialize(stream, (int)value, context);
}

public static void Deseiralize(Stream stream, out int? value, object context)
{
    int tmp;
    Deserialize(stream, out tmp, context);
    value = tmp;
}

这是我生成它的方式:

    public override void GenSerializationCode(Type type)
    {
        var underlyingType = Nullable.GetUnderlyingType(type);
        var serialize = GetSerializeCall(underlyingType);

        // Serialize(stream, (UnderlyingType)value, context);
        emit.ldarg_0()
            .ldarg_1()
            .unbox_any(underlyingType)
            .ldarg_2()
            .call(serialize)
            .ret();
    }

    public override void GenDeserializationCode(Type type)
    {
        var underlyingType = Nullable.GetUnderlyingType(type);
        var deserialize = GetDeserializeCall(underlyingType);

        // UnderlyingType tmp; Deserialize(stream, out tmp, context);
        var tmp = emit.declocal(underlyingType);
        emit.ldarg_0()
            .ldloca_s(tmp)
            .ldarg_2()
            .call(deserialize);

        // value = tmp;
        emit.ldarg_1()
            .ldloc_s(tmp)
            .stind_ref()
            .ret();
    }

我还生成了一个用于调试的程序集。我在 ILSpy 中加载它,C# 代码看起来与我的想法完全一样。但是 peverify 有话要说……

在此处输入图像描述

我想了一会儿,然后意识到这Nullable<T>是一个结构,所以我应该使用Ldarga而不是Ldarg所以我将我ldarg_1()的改为ldarga(1)

现在 peverify 给出:

[IL]: Error: [C:\Users\vexe\Desktop\MyExtensionsAndHelpers\Solution\CustomSerializer\bin\Release\SerTest.dll : FastSerializer::Serialize][offset 0x00000007][found address of value 'System.Nullable`1[System.Int32]'] Expected an ObjRef on the stack.

我认为这与Nullable<T>转换运算符有关,所以我尝试了该Value属性:

        var underlyingType = Nullable.GetUnderlyingType(type);
        var serialize = GetSerializeCall(underlyingType);
        var getValue = type.GetProperty("Value").GetGetMethod();

        // Serialize(stream, value.get_Value(), context);
        emit.ldarg_0()
            .ldarga(1)
            .call(getValue)
            .ldarg_2()
            .call(serialize)
            .ret();

peverify 对此感到高兴!

问题是,为什么在将 nullable 转换为其底层类型时,显式运算符 from 之前没有启动TNullable<T>

Ldarga此外,即使在使用而不是Ldarg在执行时,我也无法摆脱反序列化中的错误value = tmp;- 我想我可以尝试隐式转换正在做什么。即value = new Nullable<int>(tmp);,但我想找出我做错了什么。

注意:'emit' 只是我用来生成 IL 的助手。它在ILGenerator内部使用并在每次操作后返回自身,因此我可以将调用链接在一起。

编辑:这是有效的最终代码,带有注释和所有内容。

    // Note:
    // 1- IL doesn't know anything about implicit/explicit operators
    //    so we can't make use of the T to Nullable<T> nor Nullable<T> to T operators
    //    that's why we have to use the Value property when serializing and the ctor when deserializing
    // 2- Nullable<T> is a struct
    //    so we use ldarga when calling the property getter when serializing (the property getter is an instance method, so the first argument is always the 'this', but since we're dealing with structs we have to pass 'this' by ref hence ldarga)
    //    then use stobj opcode when constructing an instance when deserializing

    public override void GenSerializationCode(Type type)
    {
        var underlyingType = Nullable.GetUnderlyingType(type);
        var serialize = ctx.GetSerializeCall(underlyingType);
        var getValue = type.GetProperty("Value").GetGetMethod();

        // Serialize(stream, value.get_Value(), ctx);
        emit.ldarg_0()
            .ldarga(1)
            .call(getValue)
            .ldarg_2()
            .call(serialize)
            .ret();
    }

    public override void GenDeserializationCode(Type type)
    {
        var underlyingType = Nullable.GetUnderlyingType(type);
        var deserialize = ctx.GetDeserializeCall(underlyingType);

        // UnderlyingType tmp; Deserialize(stream, out tmp, ctx);
        var tmp = emit.declocal(underlyingType);
        emit.ldarg_0()
            .ldloca_s(tmp)
            .ldarg_2()
            .call(deserialize);

        // value = new Nullable<UnderlyingType>(tmp);
        var ctor = type.GetConstructor(new Type[] { underlyingType });
        emit.ldarg_1()
            .ldloc_s(tmp)
            .newobj(ctor)
            .stobj(type)
            .ret();
    }
}
4

2 回答 2

2

显式和隐式转换是纯粹的 C# 概念。

IL 对可空类型没有任何特殊意识(除了将它们装箱成Objects);您需要显式使用.Value或调用ctor。

例如,查看C# 编译器生成的 IL

于 2015-03-11T16:07:26.950 回答
1

运算符仅适用于 C# 和 VB.NET 编译器。IL 和 IL 世代对此一无所知。

于 2015-03-11T16:07:57.230 回答