14

CompilationRepresentationFlags.UseNullAsTrueValue可以用来

允许使用 null 作为可区分联合中的 null 鉴别器的表示形式

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

为什么这很有用?与检查联合案例(生成的Tag属性)的传统机制相比,空检查如何更好?

它可能会导致意想不到的行为:

Some(1).ToString() //"Some(1)"
None.ToString()    //NullReferenceException

编辑

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

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

let t = Z

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

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

考试:

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

结果:

真实: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();

结果是一样的:

真实:00:00:00.036,CPU:00:00:00.031,GC gen0:0,gen1:0,gen2:0

模式匹配t == null按照前一种情况和t is Z后一种情况进行编译。

4

2 回答 2

10

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

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

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

于 2012-08-14T19:39:55.953 回答
6

Jack 的回答似乎不错,但稍微扩展一下,在 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

于 2012-08-14T20:50:19.473 回答