以下代码返回Some "Test"
而不是None
。基本上,我正在尝试实现cObj?.B.A.P
的 C# 代码。
// Setup
[<AllowNullLiteral>]
type A() =
member x.P = "Test"
[<AllowNullLiteral>]
type B(a:A) =
member x.A = a
[<AllowNullLiteral>]
type C(b:B) =
member x.B = b
// Test
let aObj: A = null
let cObj = new C(new B(aObj))
let r =
cObj |> Option.ofObj
|> Option.map(fun c -> c.B)
|> Option.map(fun b -> b.A)
|> Option.map(fun a -> a.P) // Expect return None since a is null
// printfn "%A" a; will print <null>.
// How can F# got property of null object?
r
似乎 F# 不会将null
视为 Option.map 中的None
。有没有一个简单的修复程序可以让它在找到null
后立即返回 None?
与 C# 不同,F# 试图在任何地方都显式。从长远来看,这会导致更易于维护和正确的程序。
特别是,null
与Option
完全无关.null
与None
不同。None
是类型Option
的值,而null
是一个非常模糊的概念 - 可以是任何类型的值。
如果您想在参数null
时返回None
,否则Some
,您需要的是Option.bind
,而不是Option.map
。Option.bind
接受一个函数,该函数获取一个值(从前一个Option
中提取(并返回另一个Option
。像这样:
let maybeC = Option.ofObj cObj
let maybeB = maybeC |> Option.bind (c -> Option.ofObj c.B)
let maybeA = maybeB |> Option.bind (b -> Option.ofObj b.A)
let maybeP = maybeA |> Option.bind (a -> Option.ofObj a.P)
或一气呵成:
let maybeP =
Option.ofObj cObj
|> Option.bind (c -> Option.ofObj c.B)
|> Option.bind (b -> Option.ofObj b.A)
|> Option.bind (a -> Option.ofObj a.P)
如果您经常执行此类操作,则可以将Option.bind
和Option.ofObj
调用组合在一起,并将其编码为单独的函数:
let maybeNull f = Option.bind (x -> Option.ofObj (f x))
let maybeP =
Option.ofObj cObj
|> maybeNull (c -> c.B)
|> maybeNull (b -> b.A)
|> maybeNull (a -> a.P)
但是,如果你发现自己被埋没在这样的null
中,我建议也许你的领域设计没有经过深思熟虑。空值不是一个好的建模工具,应尽可能避免它们。我鼓励你重新考虑你的设计。
Option.map 的第一个参数是一个函数'T -> 'U
。 它的参数类型是'T
,而不是'T option
。 因此,在上一个 lambdafun a -> a.P
中,空参数表示类型A
的空,而不是类型A option
的空。
由于类型A
的成员P
仅返回字符串"Test",因此即使接收方为 null,调用也会成功并返回。 如果您尝试在P
的正文中使用 self 标识符,您将获得空引用异常。