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.