F#中与C#的'out'相当的语法是什么?

26
我正在将一个 C# 库重写为 F#,并需要翻译以下代码。
bool success;
instance.GetValue(0x10, out success);

在F#中,out关键字的等效语句是什么?


2
对于新访客,自F# 4.5以来,它支持托管指针,您可以使用inref(只读引用),outref(仅写引用)和byref(读/写引用)。它在参数和返回类型上都是有效的,但有某些限制以防止它们随意逃脱范围。您的代码将变为GetValue(0x10, success outref),其中success是可变的。 - Abel
4个回答

39

既然wasatz和Max Malook的回答都不完整,那么有三种调用带有out参数的方法的方式。第二种和第三种方式同样适用于ref参数。

以下示例中假设存在以下类型:

open System.Runtime.InteropServices //for OutAttribute
type SomeType() =
    member this.GetValue (key, [<Out>] success : bool byref) =
        if key = 10 then
            success <- true
            "Ten"
        else
            success <- false
            null

同时假设我们有该类型的一个实例:

let o = SomeType()

选项1

您可以通过将输出参数与返回值捆绑为元组来让F#编译器处理它:

let result1, success1 = o.GetValue 10
let result2, success2 = o.GetValue 11

在 F# 交互式窗口中运行上述代码会得到以下结果:

val success1 : bool = true
val result1 : string = "Ten"
val success2 : bool = false
val result2 : string = null

选项2

您可以使用可变值,并使用&运算符传递其地址:

let mutable success3 = false
let result3 = o.GetValue (10, &success3)
let mutable success4 = false
let result4 = o.GetValue (11, &success4)

在 F# interactive 中,结果为

val mutable success3 : bool = true
val result3 : string = "Ten"
val mutable success4 : bool = false
val result4 : string = null

如果你要委托给另一个方法处理时,使用这个选项最佳。因为你可以直接将调用方法的输出参数传递给被调用的方法。例如,如果你正在实现对IDictionary<_,_>的包装,可以编写如下的TryGetValue方法:

//...
interface IDictionary<'TKey, 'TValue> with
    member this.TryGetValue (key, value) = inner.TryGetValue (key, &value)
    //...

选项3

您可以使用一个参考单元格:

let success5 = ref false
let result5 = o.GetValue (10, success5)
let success6 = ref false
let result6 = o.GetValue (11, success6)

输出结果:

val success5 : bool ref = {contents = true;}
val result5 : string = "Ten"
val success6 : bool ref = {contents = false;}
val result6 : string = null

注意!

在使用in/out参数时,请勿像在C#中使用ref关键字。例如,以下代码不会产生预期的结果:

let success7 = false
let result7 = o.GetValue (10, ref success7)

输出结果:

val success7 : bool = false
val result7 : string = "Ten"
为什么success7保持值为false? 因为success7是一个不可变的变量。
在C#中,ref表示您正在将变量的引用作为ref参数的参数传递。它仅仅作为一种保险措施,告知调用者的程序员需要注意被调用方法可能修改该变量。但在F#中,ref会创建一个新的引用单元,其中包含所以下一个表达式的值的副本。
在这种情况下,我们创建了一个引用单元,其中包含从success7变量复制的值,但没有将该新引用单元分配给任何变量。然后,我们将该引用单元传递给GetValue方法,该方法修改引用单元的内容。因为调用方法没有指向修改后单元的变量,所以无法读取引用单元的新值。

6

你最好返回一个选项(Option)或者元组(Tuple)。因为F#有模式匹配,所以你不需要使用out参数,因为有更好的方法从函数中返回多个值。

所以,像这样做更符合习惯:

let (value, success) = instance.GetValue(0x10)

instance.GetValue是一个

unit -> ('a, bool) 

或者你可以返回一个选项并执行类似以下操作
match instance.GetValue(0x10) with
| Some value -> doStuff value
| None -> failwith "Oops!"

谢谢您的回答。我的帖子没有提到一个重要的事实,那就是我不能修改GetValue,因为它是第三方库的一部分。按照您的建议,在某些情况下包装out行为可能是有意义的。 - kasperhj
3
实际上,F#编译器会将包含out参数的方法转换为返回元组的方法。这是调用带有out参数的方法的三种方式之一。Max Malook的答案涵盖了另外一种方式。 - phoog

5

您需要使用一个参考单元格

let success = ref false
instance.GetValue(0x10, success)

// access the value
!success

2
你不一定需要使用引用单元格;这只是三种可能性之一。 - phoog

2
我认为在这里值得一提的是,输出参数的值不需要初始化。
以下操作是可行的:
let mutable success3 = Unchecked.defaultof<bool>
let result3 = o.GetValue (10, &success3)

这可能在调用带有输出参数数组的 .NET 库函数的情况下很有用,例如:

let mutable currFeatures = Unchecked.defaultof<PointF[]>
let mutable status = Unchecked.defaultof<byte[]>
let mutable trackError = Unchecked.defaultof<float32[]>

CvInvoke.CalcOpticalFlowPyrLK(
      previousFrame, 
      nextFrame, 
      previousPoints, 
      Size(15,15), 
      2, 
      MCvTermCriteria(10, 0.03), 
      //Out params 
      &currFeatures, 
      &status, 
      &trackError,
      //---------
      LKFlowFlag.UserInitialFlow)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接