[de]serialization コードを生成するために 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
ときでも、Deserialize のエラーを取り除くことができませんでした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();
}
}