引用单元格 (F#)
“引用单元格”是一些存储位置,您可以利用它们来创建具有引用语义的可变值。
ref expression
备注
可以在值前面使用 ref 运算符来创建用于封装该值的新引用单元格。 然后,您可以更改基础值,因为该值是可变的。
引用单元格容纳实际值;它不仅仅是一个地址。 使用 ref 运算符创建引用单元格时,将以封装的可变值的形式创建基础值的副本。
可以使用 !(感叹号)运算符取消对引用单元格的引用。
下面的代码示例阐释了引用单元格的声明和用法。
// Declare a reference.
let refVar = ref 6
// Change the value referred to by the reference.
refVar := 50
// Dereference by using the ! operator.
printfn "%d" !refVar
输出为 50。
引用单元格是 Ref 泛型记录类型的实例,声明方式如下所示。
type Ref<'a> =
{ mutable contents: 'a }
类型 'a ref 是 Ref<'a> 的同义词。 IDE 中的编译器和 IntelliSense 显示此类型的前者,而基础定义是后者。
ref 运算符创建新引用单元格。 下面的代码声明 ref 运算符。
let ref x = { contents = x }
下表显示了可用于引用单元格的功能。
运算符、成员或字段 |
说明 |
类型 |
定义 |
---|---|---|---|
!(取消引用运算符) |
返回基础值。 |
'a ref -> 'a |
let (!) r = r.contents |
:=(赋值运算符) |
更改基础值。 |
'a ref -> 'a -> unit |
let (:=) r x = r.contents <- x |
ref(运算符) |
将值封装到新的引用单元格中。 |
'a -> 'a ref |
let ref x = { contents = x } |
Value(属性) |
获取或设置基础值。 |
unit -> 'a |
member x.Value = x.contents |
contents(记录字段) |
获取或设置基础值。 |
'a |
let ref x = { contents = x } |
可通过多种方式来访问基础值。 取消引用运算符 (!) 返回的值不是可赋值的值。 因此,如果要修改基础值,您必须改用赋值运算符 (:=)。
Value 属性和 contents 字段都是可赋值的值。 因此,您可以使用它们来访问或更改基础值,如下面的代码所示。
let xRef : int ref = ref 10
printfn "%d" (xRef.Value)
printfn "%d" (xRef.contents)
xRef.Value <- 11
printfn "%d" (xRef.Value)
xRef.contents <- 12
printfn "%d" (xRef.contents)
输出如下所示。
10
10
11
12
提供字段 contents 的目的是为了与其他版本的 ML 兼容,并且该字段将在编译过程中产生警告。 若要禁用警告,请使用 --mlcompatibility 编译器选项。 有关更多信息,请参见编译器选项 (F#)。
示例
下面的代码阐释了参数传递中引用单元格的用法。 Incrementor 类型具有方法 Increment,该方法接受参数类型中包括 byref 的参数。 参数类型中的 byref 指示调用方必须传递引用单元格或指定类型的典型变量的地址,本例中为 int。 其余的代码阐释如何使用这两种类型的参数调用 Increment,并演示如何对变量使用 ref 运算符来创建引用单元格 (ref myDelta1)。 然后,它演示了如何使用 address-of 运算符 (&) 来生成相应的参数。 最后,将使用通过 let 绑定声明的引用单元格,再次调用 Increment 方法。 代码的最后一行演示如何使用 ! 运算符来取消引用用于打印的引用单元格。
type Incrementor(delta) =
member this.Increment(i : int byref) =
i <- i + delta
let incrementor = new Incrementor(1)
let mutable myDelta1 = 10
incrementor.Increment(ref myDelta1)
// Prints 10:
printfn "%d" myDelta1
let mutable myDelta2 = 10
incrementor.Increment(&myDelta2)
// Prints 11:
printfn "%d" myDelta2
let refInt = ref 10
incrementor.Increment(refInt)
// Prints 11:
printfn "%d" !refInt
有关如何通过引用传递的更多信息,请参见形参和实参 (F#)。
备注
C# 程序员应知道 ref 在 F# 中的工作方式与在 C# 中的工作方式不同。例如,在传递参数时使用 ref 的效果在 F# 中与 C# 中的效果不同。
引用单元格与可变变量
引用单元格和可变变量通常可在相同情形下使用。 但是,在有些情形下无法使用可变变量,因此必须改用引用单元格。 通常,在编译器接受的情况下,您应优先使用可变变量。 但是,在生成闭包的表达式中,编译器将报告您不能使用可变变量。 “闭包”是某些 F# 表达式(例如 lambda 表达式、序列表达式、计算表达式)生成的本地函数,以及使用部分应用的参数的扩充函数。 系统将存储这些表达式生成的闭包以供以后计算。 此过程与可变变量不兼容。 因此,如果您在此类表达式中需要可变状态,则必须使用引用单元格。 有关闭包的更多信息,请参见“闭包 (F#)”。
下面的代码示例演示了您必须使用引用单元格的方案。
// Print all the lines read in from the console.
let PrintLines1() =
let mutable finished = false
while not finished do
match System.Console.ReadLine() with
| null -> finished <- true
| s -> printfn "line is: %s" s
// Attempt to wrap the printing loop into a
// sequence expression to delay the computation.
let PrintLines2() =
seq {
let mutable finished = false
// Compiler error:
while not finished do
match System.Console.ReadLine() with
| null -> finished <- true
| s -> yield s
}
// You must use a reference cell instead.
let PrintLines3() =
seq {
let finished = ref false
while not !finished do
match System.Console.ReadLine() with
| null -> finished := true
| s -> yield s
}
在前面的代码中,引用单元格 finished 包括在本地状态中,也就是说,闭包中的变量是完全在表达式(本例中为序列表达式)中创建和使用的。 请考虑在变量为非局部变量时发生的情况。 闭包也可访问非本地状态,但如果出现这种情况,则会按值复制和存储变量。 此过程称为“值语义”。 这意味着复制时的值将会存储,而对变量进行的任何后续更改将不会反映出来。 如果要跟踪非局部变量的更改,或者,换句话说,如果需要通过使用“引用语义”与非本地状态交互的闭包,则必须使用引用单元格。
下面的代码示例演示了引用单元格在闭包中的用法。 在本例中,闭包是通过部分应用函数参数生成的。
// The following code demonstrates the use of reference
// cells to enable partially applied arguments to be changed
// by later code.
let increment1 delta number = number + delta
let mutable myMutableIncrement = 10
// Closures created by partial application and literals.
let incrementBy1 = increment1 1
let incrementBy2 = increment1 2
// Partial application of one argument from a mutable variable.
let incrementMutable = increment1 myMutableIncrement
myMutableIncrement <- 12
// This line prints 110.
printfn "%d" (incrementMutable 100)
let myRefIncrement = ref 10
// Partial application of one argument, dereferenced
// from a reference cell.
let incrementRef = increment1 !myRefIncrement
myRefIncrement := 12
// This line also prints 110.
printfn "%d" (incrementRef 100)
// Reset the value of the reference cell.
myRefIncrement := 10
// New increment function takes a reference cell.
let increment2 delta number = number + !delta
// Partial application of one argument, passing a reference cell
// without dereferencing first.
let incrementRef2 = increment2 myRefIncrement
myRefIncrement := 12
// This line prints 112.
printfn "%d" (incrementRef2 100)