F#/ .NET null实例奇怪

我有这个C#DLL:

namespace TestCSProject { public class TestClass { public static TestClass Instance = null; public int Add(int a, int b) { if (this == null) Console.WriteLine("this is null"); return a + b; } } } 

这个引用DLL的F#app:

 open TestCSProject printfn "%d" (TestClass.Instance.Add(10,20)) 

没有人启动Instance静态变量。 猜猜F#app的输出是什么?

这是空的
三十
按任意键继续 。  。  。

经过几次测试后,我发现除非我使用this (例如访问实例字段),否则我不会得到NullReferenceExpcetion。

这是F#编译/ CLR中的预期行为还是差距?

如果你看一下IL,我怀疑你会发现它正在使用call而不是callvirt 。 C#编译器总是使用callvirt ,即使对于非虚方法也是如此,因为它会强制进行无效检查。

这是一个错误吗? 嗯,不一定。 它取决于F#语言规范对null引用的方法调用的说明。 它完全有可能声明该方法将被调用(非虚拟),并带有一个空的“this”引用,这正是发生的事情。

C#碰巧指定这种解除引用会抛出NullReferenceException ,但这是一种语言选择。

我怀疑F#方法可能会更快一些,因为缺乏无效性检查……并且不要忘记F#中的空引用不如C#中的“预期”……这可能解释了采用的不同方法这里。 当然,它可能只是一种疏忽。

编辑:我不是阅读F#规范的专家,但第6.9.6节至少告诉我这是一个错误:

6.9.6评估方法应用

对于方法的详细应用,表达式的详细forms将是expr.M(args)或M(args)。

  • (可选)expr和args按从左到右的顺序进行计算,成员的主体在具有映射到相应参数值的forms参数的环境中进行求值。

  • 如果expr求值为null,则引发NullReferenceException。

  • 如果该方法是虚拟分派槽(即,声明为abstract的方法),则根据expr值的分派映射选择成员的主体。

这是否算作一个精心设计的应用程序是有点超出我的,我害怕…但我希望这至少有点帮助。

我认为F#团队认为这种行为是一个可能在未来版本中发生变化的错误。

有时,类可能会将方法从非虚拟(在版本N中)更改为虚拟版本(在版本N + 1中)。 这是一个“突破性变化”吗? 如果针对原始类编译的代码使用call而不是callvirt 。 “突破性变革”的概念主要是程度而非善意 ; 非虚拟到虚拟的变化是一个“次要的”突破性变化。 在任何情况下,.NET Framework中的某些类都表现出这种变化,因此您遇到类似于“脆弱基类”的问题。 见证这一点,F#团队打算改变编译器callvirt以使用像C#和VB那样的callvirt 。 假设发生这种情况,一些不太可能的程序,例如此repro中的程序,将在使用未来版本的F#编译器进行编译时改变行为。