I have the following C# classes
public class BadClass
{
public BadClass(int? bad = 1)
{
}
}
public class GoodClass
{
public GoodClass(int? good = null)
{
}
}
As you can see they both have optional nullable parameters as part of their constructors, the only difference is that BadClass has the parameter default set to something other than null.
If I attempt to create an instance of these classes in F# this is what I get:
This works fine:
let g = GoodClass()
This throws a NullReferenceException:
let b = BadClass()
And this throws an AccessViolationException
let asyncB = async { return BadClass() } |> Async.RunSynchronously
Any idea why this is?
EDIT
Using ILSpy to decompile it this is the output of the F#
The C# classes are in an assembly called InteopTest [sic]
ILSpy to C#
GoodClass g = new GoodClass(null);
BadClass b = new BadClass(1);
FSharpAsyncBuilder defaultAsyncBuilder = ExtraTopLevelOperators.DefaultAsyncBuilder;
FSharpAsync<BadClass> fSharpAsync = defaultAsyncBuilder.Delay<BadClass>(new Program.asyncB@10(defaultAsyncBuilder));
FSharpAsync<BadClass> computation = fSharpAsync;
BadClass asyncB = FSharpAsync.RunSynchronously<BadClass>(computation, null, null);
FSharpFunc<string[], Unit> fSharpFunc = ExtraTopLevelOperators.PrintFormatLine<FSharpFunc<string[], Unit>>(new PrintfFormat<FSharpFunc<string[], Unit>, TextWriter, Unit, Unit, string[]>("%A"));
fSharpFunc.Invoke(argv);
return 0;
and this is the IL
.method public static
int32 main (
string[] argv
) cil managed
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x2050
// Code size 92 (0x5c)
.maxstack 5
.entrypoint
.locals init (
[0] class [InteopTest]InteopTest.GoodClass g,
[1] valuetype [mscorlib]System.Nullable`1<int32>,
[2] class [InteopTest]InteopTest.BadClass b,
[3] class [InteopTest]InteopTest.BadClass asyncB,
[4] class [FSharp.Core]Microsoft.FSharp.Control.FSharpAsync`1<class [InteopTest]InteopTest.BadClass>,
[5] class [FSharp.Core]Microsoft.FSharp.Control.FSharpAsyncBuilder builder@,
[6] class [FSharp.Core]Microsoft.FSharp.Control.FSharpAsync`1<class [InteopTest]InteopTest.BadClass>,
[7] class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string[], class [FSharp.Core]Microsoft.FSharp.Core.Unit>,
[8] string[]
)
IL_0000: nop
IL_0001: ldloca.s 1
IL_0003: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0009: ldloc.1
IL_000a: newobj instance void [InteopTest]InteopTest.GoodClass::.ctor(valuetype [mscorlib]System.Nullable`1<int32>)
IL_000f: stloc.0
IL_0010: ldc.i4.1
IL_0011: newobj instance void [InteopTest]InteopTest.BadClass::.ctor(valuetype [mscorlib]System.Nullable`1<int32>)
IL_0016: stloc.2
IL_0017: call class [FSharp.Core]Microsoft.FSharp.Control.FSharpAsyncBuilder [FSharp.Core]Microsoft.FSharp.Core.ExtraTopLevelOperators::get_DefaultAsyncBuilder()
IL_001c: stloc.s builder@
IL_001e: ldloc.s builder@
IL_0020: ldloc.s builder@
IL_0022: newobj instance void Program/asyncB@10::.ctor(class [FSharp.Core]Microsoft.FSharp.Control.FSharpAsyncBuilder)
IL_0027: callvirt instance class [FSharp.Core]Microsoft.FSharp.Control.FSharpAsync`1<!!0> [FSharp.Core]Microsoft.FSharp.Control.FSharpAsyncBuilder::Delay<class [InteopTest]InteopTest.BadClass>(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Control.FSharpAsync`1<!!0>>)
IL_002c: stloc.s 4
IL_002e: ldloc.s 4
IL_0030: stloc.s 6
IL_0032: ldloc.s 6
IL_0034: ldnull
IL_0035: ldnull
IL_0036: call !!0 [FSharp.Core]Microsoft.FSharp.Control.FSharpAsync::RunSynchronously<class [InteopTest]InteopTest.BadClass>(class [FSharp.Core]Microsoft.FSharp.Control.FSharpAsync`1<!!0>, class [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1<int32>, class [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1<valuetype [mscorlib]System.Threading.CancellationToken>)
IL_003b: stloc.3
IL_003c: ldstr "%A"
IL_0041: newobj instance void class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string[], class [FSharp.Core]Microsoft.FSharp.Core.Unit>, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit, string[]>::.ctor(string)
IL_0046: call !!0 [FSharp.Core]Microsoft.FSharp.Core.ExtraTopLevelOperators::PrintFormatLine<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string[], class [FSharp.Core]Microsoft.FSharp.Core.Unit>>(class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<!!0, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>)
IL_004b: stloc.s 7
IL_004d: ldarg.0
IL_004e: stloc.s 8
IL_0050: ldloc.s 7
IL_0052: ldloc.s 8
IL_0054: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string[], class [FSharp.Core]Microsoft.FSharp.Core.Unit>::Invoke(!0)
IL_0059: pop
IL_005a: ldc.i4.0
IL_005b: ret
} // end of method Program::main
To me, this looks like a bug in the F# compiler. If you write some extra C#:
public class OtherClass
{
private static BadClass _bc = new BadClass();
}
and look at the IL, you'll see this:
// push 1 on the stack
IL_0000: ldc.i4.1
// call Nullable<int32> constructor, leaving object on stack
IL_0001: newobj instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
// call BadClass constructor with int?
IL_0006: newobj instance void Nullabool.BadClass::.ctor(valuetype [mscorlib]System.Nullable`1<int32>)
// store in _bc
IL_000b: stsfld class Nullabool.BadClass Nullabool.OtherClass::_bc
Which clearly instantiates Nullable`1 with 1.
Whereas the F# code for b ends up like this:
// push a 1 on the stack
IL_0016: ldc.i4.1
// call BadClass constructor with 1 - this fails IL verification
IL_0017: newobj instance void [Nullabool]Nullabool.BadClass::.ctor(valuetype [mscorlib]System.Nullable`1<int32>)
which leaves an int
on the stack instead of int?
. When I try to run this code, I get an IL verification error since the type doesn't match.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With