为 Nullable<T> 序列化生成 IL?

Generating IL for Nullable<T> serialization?

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

对于可空值,我想我可以生成以下内容(将 int? 作为 ex)(假设我们已经生成了 [de] 序列化 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 对此很高兴!

问题是,为什么之前从 TNullable<T> 的显式运算符在将可空对象转换为其基础类型时没有启动?

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

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

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

    // 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();
    }
}

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

IL 对可空类型没有任何特殊认识(除了将它们装箱到 Object 中);您需要明确使用 .Value 或调用 ctor.

例如,查看 IL generated by the C# compiler

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