F# under the covers XVI -- Constructor weirdness
Occasionally, even in F#, one needs to do OO stuff, like implementing a concrete subclass of some abstract framework type to feed into some other framework API. In my case, I recently needed to add a SerializationBinder to a BinaryFormatter to handle assembly versioning.
So of course I wrote
formatter.Binder <- { new System.Runtime.Serialization.SerializationBinder()
with member self.BindToType (a:string, t:string) = ... }which worked perfectly happily, but threw up a warning from Gendarme about suspicious recursion in the constructor.
So I decompiled the type to find it looked like
[Serializable]
[StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)]
[CompilationMapping(SourceConstructFlags.Closure)]
internal sealed class ReadResults@64 : SerializationBinder
{
public ReadResults@64()
{
((SerializationBinder)this)..ctor();
}
public override Type BindToType(string _arg1, string _arg2)
{
...
}
}or, as IL
.method public specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x3ce0
// Code size 9 (0x9)
.maxstack 8
IL_0000: ldarg.0
IL_0001: callvirt instance void [mscorlib]System.Runtime.Serialization.SerializationBinder::.ctor()
IL_0006: ldarg.0
IL_0007: pop
IL_0008: ret
}Writing the type as an explicit class, like
type UpdateBinder () = inherit System.Runtime.Serialization.SerializationBinder() override self.BindToType ...
yields exactly the same sort of IL.
Revising the class yet again as
type MonoTypeBinder (``type``:Type) =
inherit System.Runtime.Serialization.SerializationBinder()
override self.BindToType (_:string, _:string) =
``type``because I only have one type of interest, did produce the expected decompiled constructor, looking like
public MonoTypeBinder(Type type)
: this()
{
this.type = type;
}even though the actual IL just adds the field assignment
IL_0000: ldarg.0 IL_0001: callvirt instance void [mscorlib]System.Runtime.Serialization.SerializationBinder::.ctor() IL_0006: ldarg.0 IL_0007: pop IL_0008: ldarg.0 IL_0009: ldarg.1 IL_000a: stfld class [mscorlib]System.Type AltCover.MonoTypeBinder::'type' IL_000f: ret
However, now we've changed the signature, the call no longer looks like a recursion. And, for once, this is a case where the virtual call in a constructor is safe.
By contrast, a C# equivalent
class UpdateBinder : System.Runtime.Serialization.SerializationBinder
{
public override Type BindToType(string a, string t)
{
...
}
}generates a default constructor with IL that makes a non-virtual call to the base type
IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Runtime.Serialization.SerializationBinder::.ctor() IL_0006: nop IL_0007: ret
and the call remains non-virtual when adding a constructor argument.
No comments :
Post a Comment