我正在编写自己的序列化程序,它发出 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 之前没有启动T
?Nullable<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();
}
}