Generating IL for Nullable<T> serialization?

1

I'm writing my own serializer that emits IL to generate the [de]serialization codes.

For nullables, I thought I could generate the following (take int? as an ex) (assuming we already generated methods to [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;
}

Here's how I generate that:

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

I also generate an assembly for debugging. I load it up in ILSpy and the C# code looks exactly like what I had in mind. But peverify had something else to say...

enter image description here

I thought about it for a minute, then realized that Nullable<T> is a struct, so I should use Ldarga instead of Ldarg so I changed my ldarg_1() to ldarga(1)

Now peverify gives:

[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.

I thought it's something to do with Nullable<T> conversion operators so I tried the Value property:

        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 is happy about this!

The question is, why didn't the explicit operator from T to Nullable<T> kick in previously when casting the nullable to its underlying type?

Also, I wasn't able to get rid of the error in Deserialize even when using Ldarga instead of Ldarg when doing value = tmp;- I guess I could try what the implicit conversion is doing. i.e. value = new Nullable<int>(tmp); but I would like to find out what I did wrong.

Note: 'emit' is just a helper I use to generate IL. It uses an ILGenerator internally and returns itself after each operation so I can chain together calls.

EDIT: here's the final code that worked, with notes and all.

    // 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#
nullable
reflection.emit
il
conversion-operator
asked on Stack Overflow Mar 11, 2015 by vexe • edited Mar 11, 2015 by vexe

2 Answers

2

Explicit and implicit conversions are a purely C# concept.

IL does not have any special awareness of nullable types (except for boxing them into Objects); you need to explicitly use .Value or call the ctor.

For examples, look at the IL generated by the C# compiler.

answered on Stack Overflow Mar 11, 2015 by SLaks
1

Operators is only for the C# and VB.NET compilers. IL and IL generations knows nothing about it.

answered on Stack Overflow Mar 11, 2015 by leppie

User contributions licensed under CC BY-SA 3.0