是的。欢迎来到成员约束、ref 和 byref 值的世界。
let inline tryParseWithDefault
defaultVal
text
: ^a when ^a : (static member TryParse : string * ^a byref -> bool)
=
let r = ref defaultVal
if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r.contents))
then !r
else defaultVal
-
defaultVal 和 text 是形式参数,将被推断。这里,text 已经被限制为string,因为它被用作调用静态方法SomeType.TryParse 的第一个参数,稍后会解释。 defaultVal 被限制为 ^a 的任何值,因为它是每个 if..then..else 表达式的可能结果值。
-
^a 是一个静态解析的类型参数(相对于'a 形式的泛型类型参数)。特别是,^a 将在编译时解析为特定类型。因此,托管它的函数必须标记为inline,这意味着函数的每次调用都将成为该函数体的就地替换,其中每个静态类型参数都将成为特定类型;在这种情况下,无论defaultVal 是什么类型。没有限制defaultVal 的可能类型的基本类型或接口类型约束。但是,您可以提供静态和实例成员约束,例如此处所做的。具体来说,结果值(以及defaultVal 的类型)显然必须有一个名为TryParse 的静态成员,它接受string 和对该类型可变实例的引用,并返回boolean价值。此约束由以: ^a when ... 开头的行上声明的返回类型明确表示。 defaultVal 本身是一个可能的结果这一事实将其限制为与 ^a 相同的类型。 (该约束也隐含在整个函数的其他地方,如下所述,这是不必要的)。
-
: ^a when ^a : (static .... 将结果类型 ^a 描述为具有类型为 string * ^a byref -> bool 的名为 TryParse 的静态成员。也就是说,结果类型必须有一个名为TryParse 的静态成员,它接受一个string,一个对自身实例的引用(因此是一个可变实例),并将返回一个boolean 值。此描述是 F# 如何在 DateTime、Int32、TimeSpan 等类型上匹配 TryParse 的 .Net 定义。请注意,byref 是 F# 等效于 C# 的 out 或 ref 参数修饰符。
-
let r = ref defaultVal 创建一个引用类型并将提供的值 defaultVal 复制到其中。 ref 是 F# 创建可变类型的方式之一。另一个是mutable 关键字。不同之处在于 mutable 将其值存储在堆栈中,而 ref 将其存储在主内存/堆中并保存一个地址(在堆栈上)。最新版本的 F# 将寻求根据上下文自动将可变指定升级为 ref,从而允许您仅根据可变编码进行编码。
-
if (^a : (static... 是针对静态推断类型^a 上的 TryParse 方法的调用结果的 if 语句。此 TryParse 根据其 (string * ^a byref) 签名传递,(text, &r.contents)。这里,&r.contents 根据 TryParse 的期望提供了对 r 可变内容的引用(模拟 C# 的 out 或 ref 参数)。请注意,我们在这里没有保留,与 .Net 框架互操作的某些 F# 细节并没有延伸到这么远;特别是,将空格分隔的 F# 参数作为元组自动汇总为 .net 框架函数参数是不可用的。因此,参数作为元组(text, &r.contents) 提供给函数。
-
!r 是您读取参考值的方式。 r.Value 也可以。
.Net 提供的TryParse 方法似乎总是为 out 参数设置一个值。因此,并不严格要求默认值。但是,您需要一个结果值持有者r,并且它必须有一个初始值,甚至是 null。我不喜欢空。当然,另一种选择是对 ^a 施加另一个约束,这需要某种默认值属性。
以下后续解决方案通过使用Unchecked.defaultof< ^a > 从“推断结果”类型派生合适的占位符值(是的,感觉就像魔术)来消除对默认参数的需要。它还使用Option 类型来表征获取结果值的成功和失败。因此,结果类型为^a option。
tryParse
text
: ^a option when ^a : (static member TryParse : string * ^a byref -> bool)
=
let r = ref Unchecked.defaultof< ^a >
if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r.contents))
then Some (!r)
else None
并且,根据@kvb 的建议,以下简洁是可能的。在这种情况下,使用类型推断来规定^a 的类型约束,因为它在if (^a : ...)) 表达式中调用,并且还为TryParse 的out 参数建立可变缓冲区r 的类型。 I have since come to learn this is how FsControl does some of it's magic
let inline tryParse text : ^a option =
let mutable r = Unchecked.defaultof<_>
if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r))
then Some r
else None
let inline tryParseWithDefault defaultVal text : ^a =
match tryParse text with
| Some d -> d
| _ -> defaultVal
其中的用法是……
> let x:DateTime option = tryParse "December 31, 2014";;
val x : DateTime option = Some 2014-12-31 12:00:00 a.m.
> let x:bool option = tryParse "false";;
val x : bool option = Some false
> let x:decimal option = tryParse "84.32";;
val x : decimal option = Some 84.32M
对于对实例成员使用类型约束的情况,例如 Fsharp 的动态成员查找运算符 ? 的类型约束,这样其目标的类型必须包含 FindName:string -> obj 成员以用于解析成员查找请求,语法如下:
let inline (?) (targetObj:^a) (property:string) : 'b =
(^a : (member FindName:string -> obj) (targetObj, property)) :?> 'b
注意:
- 实例方法的签名必须明确指定它们的
self对象,这通常是对象方法的隐藏第一个参数
- 此解决方案还将结果提升为
'b 的类型
示例用法如下:
let button : Button = window?myButton
let report : ReportViewer = window?reportViewer1