为什么None表示为null?

Dan*_*iel 14 f# discriminated-union

CompilationRepresentationFlags.UseNullAsTrueValue 可以用来

允许使用null作为歧视联盟中的否定鉴别器的表示

Option.None 是这方面最突出的例子.

为什么这有用?如何检查空检查比检查联合情况(生成的Tag属性)的传统机制更好?

它可能导致意外的行为:

Some(1).ToString() //"Some(1)"
None.ToString()    //NullReferenceException
Run Code Online (Sandbox Code Playgroud)

编辑

我测试了Jack的断言,即与null相比,而不是静态只读字段更快.

[<CompilationRepresentation(CompilationRepresentationFlags.UseNullAsTrueValue)>]
type T<'T> =
  | Z
  | X of 'T

let t = Z
Run Code Online (Sandbox Code Playgroud)

使用ILSpy,我可以看到t编译为null(如预期的那样):

public static Test.T<a> t<a>()
{
    return null;
}
Run Code Online (Sandbox Code Playgroud)

考试:

let mutable i = 0
for _ in 1 .. 10000000 do
  match t with
  | Z -> i <- i + 1
  | _ -> ()
Run Code Online (Sandbox Code Playgroud)

结果:

Real:00:00:00.036,CPU:00:00:00.046,GC gen0:0,gen1:0,gen2:0

如果CompilationRepresentation删除该属性,则t成为静态只读字段:

public static Test.T<a> t<a>()
{
    return Test.T<a>.Z;
}

public static Test.T<T> Z
{
    [CompilationMapping(SourceConstructFlags.UnionCase, 0)]
    get
    {
        return Test.T<T>._unique_Z;
    }
}

internal static readonly Test.T<T> _unique_Z = new Test.T<T>._Z();
Run Code Online (Sandbox Code Playgroud)

结果是一样的:

Real:00:00:00.036,CPU:00:00:00.031,GC gen0:0,gen1:0,gen2:0

模式匹配t == null按前一种情况和t is Z后一种情况编译.

Jac*_* P. 10

F#编译器有时null用作None的表示,因为它比实际创建FSharpOption <'T>的实例并检查Tag属性更有效.

想一想 - 如果你有一个不允许为空的普通F#类型(如记录),那么任何指向该类型实例的指针(CLR内部使用的指针)都将永远不会NULL.同时,如果T是可以表示n状态的类型,则T option可以表示n+1状态.因此,使用null作为None的表示只是利用了一个额外的状态值,这是因为F#类型不允许为空.

如果您想尝试关闭此行为(对于普通的F#类型),您可以应用[<AllowNullLiteral(true)>]它们.


kvb*_*kvb 6

杰克的答案似乎很好,但为了扩展一点,在IL级别,CLR提供了一个特定的操作码,用于加载空值(ldnull)和有效的测试方法(ldnull后跟beq/ bne.un/ ceq/ cgt.un).当JITted时,这些应该比取消引用Tag属性和相应的分支更有效.虽然每次呼叫节省可能很小,但选项类型的使用频率足以使累积节省可能很大.

当然,正如您所指出的,存在权衡:从中继承的方法obj可能会抛出空引用异常.在处理F#值时,这是使用string x/ hash x/ x=y而不是x.ToString()/ x.GetHashCode()/的一个很好的理由x.Equals(y).可悲的是,没有(可能)等价x.GetType()于表示的值null.

  • Daniel,只需这样思考:需要发出什么底层CPU指令(由JIT编译器)然后在运行时执行以进行空检查和类型测试.空检查可以编译为单个CPU指令,类型测试几乎总是不止于此,因为它需要调用CLR来执行类型测试.即使他们做了一些非常高级的优化,它仍然需要多个CPU指令. (2认同)