F#与C#类互操作,它具有可选的可为空参数设置为null但是null导致NullReferenceException/AccessViolationException

TWi*_*ars 7 c# f# interop access-violation nullreferenceexception

我有以下C#类

public class BadClass
{
    public BadClass(int? bad = 1)
    {
    }
}

public class GoodClass
{
    public GoodClass(int? good = null)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它们都具有可选的可空参数作为其构造函数的一部分,唯一的区别是BadClass将参数default设置为null以外的其他值.

如果我尝试在F#中创建这些类的实例,这就是我得到的:

这很好用:

let g = GoodClass()
Run Code Online (Sandbox Code Playgroud)

这会抛出NullReferenceException:

let b = BadClass()
Run Code Online (Sandbox Code Playgroud)

这会引发AccessViolationException

let asyncB = async { return BadClass() } |> Async.RunSynchronously
Run Code Online (Sandbox Code Playgroud)

知道为什么会这样吗?

编辑

使用ILSpy对其进行反编译,这是F#的输出

C#类位于一个名为InteopTest的程序集中[原文如此]

ILSpy到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;
Run Code Online (Sandbox Code Playgroud)

这就是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
Run Code Online (Sandbox Code Playgroud)

pli*_*nth 6

对我来说,这看起来像是F#编译器中的一个错误.如果你写一些额外的C#:

public class OtherClass
{
    private static BadClass _bc = new BadClass();
}
Run Code Online (Sandbox Code Playgroud)

看看IL,你会看到这个:

// 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
Run Code Online (Sandbox Code Playgroud)

其中明确地将Nullable`1实例化为1.

而b的F#代码最终会像这样:

// 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>)
Run Code Online (Sandbox Code Playgroud)

它留下int堆栈而不是int?.当我尝试运行此代码时,由于类型不匹配,我收到IL验证错误.

  • 这确实是一个编译器错误,一段时间后独立报告给fsbugs@Microsoft.com.它只发生在你有可空的可选args时,你在调用中省略了一些它们(即填充所有可选的args都可以).这不是3.1中的新错误,它已经存在了一段时间.对于那些感兴趣的人,问题出在[tc.fs](http://fsharppowerpack.codeplex.com/SourceControl/latest#compiler/3.1/Nov2013/src/fsharp/tc.fs),它处理可选的args.搜索`Constant fieldInit`. (6认同)