考虑:
> Unchecked.defaultof<list<int>>;;
val it : int list = null
> 2 :: 1 :: Unchecked.defaultof<list<int>>;;
val it : int list = [2]
这种行为背后的原因是什么?我希望最终列表是 [2;1] 或引发异常。
考虑:
> Unchecked.defaultof<list<int>>;;
val it : int list = null
> 2 :: 1 :: Unchecked.defaultof<list<int>>;;
val it : int list = [2]
这种行为背后的原因是什么?我希望最终列表是 [2;1] 或引发异常。
这是一个非常了不起的问题。我查看了 F# 列表的编译方式并解释了行为。我不会认为这是错误(这就是为什么defaultof
在Unchecked
模块中,它可能会烧毁您的计算机!)
首先,空列表[]
不像null
F# 2.0 中那样表示,因为那样您将无法在空列表上调用方法 - 这会破坏很多事情(例如将空列表传递给任何Seq
函数)。顺便说一句,该None
值表示为null
效率,这意味着即使它有意义option<'a>
也无法实现接口。seq<'a>
因此,F# 使用特殊值list.Empty
来表示空列表。现在该值是与表示非空列表的值相同类型的实例(类型为FSharpList<'T>
)。唯一的区别是实例的head
和tail
字段都是null
.
现在,您也许可以看到这是怎么回事 - 一个空列表本质上被编译为该字段所在的对象this.tail
(null
并且该this.head
字段也是null
如此,但这并不重要)。
当字段为时,该list.Tail
属性将引发异常(因为这意味着您this.tail
正在null
获得一个空列表的尾部 - 这是一个错误),但还有一个内部属性list.TailOrNull
无需检查即可返回值并用于编译模式匹配。
例如,这是一个简单的iter
函数:
let rec iter (xs:int list) =
match list with
| x::xs -> Console.WriteLine(x); iter xs
| [] -> ()
这被编译为以下 C# 代码:
if (list.TailOrNull != null) {
Console.WriteLine(list.HeadOrDefault);
iter(list.TailOrNull);
}
这通过检查其是否为来检查是否为list
空列表。如果是,则它知道当前值是表示空列表的值(并且它不打印它,因为它假设这也是)。list.TailOrNull
null
list
list.Empty
HeadOrDefault
null
如果您使用显式defaultof
创建null
值,则为42::null
您提供一个对象 where TailOrNull
is null
but HeadOrDefault
is 42
。这就是为什么不打印值的原因。
TL;DR - 不要Unchecked.defaultof
与 F# 类型一起使用。你永远不知道会发生什么。