简体   繁体   English

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

[英]F# interop with C# class that has an optional nullable parameter set to anything but null causes NullReferenceException / AccessViolationException

I have the following C# classes 我有以下C#类

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

If I attempt to create an instance of these classes in F# this is what I get: 如果我尝试在F#中创建这些类的实例,这就是我得到的:

This works fine: 这很好用:

let g = GoodClass()

This throws a NullReferenceException: 这会抛出NullReferenceException:

let b = BadClass()

And this throws an AccessViolationException 这会引发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# 使用ILSpy对其进行反编译,这是F#的输出

The C# classes are in an assembly called InteopTest [sic] C#类位于一个名为InteopTest的程序集中[原文如此]

ILSpy to C# 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;

and this is the IL 这就是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. 对我来说,这看起来像是F#编译器中的一个错误。 If you write some extra C#: 如果你写一些额外的C#:

public class OtherClass
{
    private static BadClass _bc = new BadClass();
}

and look at the IL, you'll see this: 看看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

Which clearly instantiates Nullable`1 with 1. 其中明确地将Nullable`1实例化为1。

Whereas the F# code for b ends up like this: 而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>)

which leaves an int on the stack instead of int? 哪个在堆栈上留下一个int而不是int? . When I try to run this code, I get an IL verification error since the type doesn't match. 当我尝试运行此代码时,由于类型不匹配,我收到IL验证错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM