引用单元格 (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)

请参见

参考

符号和运算符参考 (F#)

概念

形参和实参 (F#)

其他资源

F# 语言参考