这是计算表达式的候选者吗?

时间:2011-05-09 15:34:48

标签: c# f# monads

我有以下c#代码,它会检查权限。我想知道,当转换为f#时,计算表达式是否是一种分解空检查的方法。

bool ShouldGrantPermission(ISecurityService security, User user, Item item) {
  return user != null && item != null && user.Id == item.AuthorId
    && security.Does(user).Have(MyPermission).On(item);
}

我想要注意的是,如果任何项为null,则ISecurityService API当前返回false。但它会进行数据库调用,因此这里的代码检查null然后执行id检查,因为在大多数情况下,这将返回false并避免数据库调用。

2 个答案:

答案 0 :(得分:8)

您可以定义一个隐藏null检查的计算构建器,但它不会为您提供非常方便的语法,因此我可能不会这样写。如果有一些更轻量级的语法,那将是很酷的,因为它会非常有用。此外,计算构建器只传播null,因此您将以Nullable<bool>类型的结果结束:

nullable { let! u = user
           let! i = item
           return u.Id == i.AuthorId && security.Does(user).Have(MyPermission).On(i) }

这个想法是let!操作仅在参数不是null时调用其余的计算。当它为null时,它会立即返回null作为整体结果。

我认为你没有太多办法让代码变得更好。当然,如果它都是用F#编写的,那么这些值都不能是null(因为F#声明的类型不允许null值),但这是一个不同的故事。

F#中的另一种方法是声明仅在值不是null时才匹配的活动模式。这样做的好处是,您不会在代码中包含任何可能具有null值的变量,因此不存在使用错误变量并获取NullReferenceException的危险:

let shouldGrantPermission = function
  | NotNull(security:ISecurityService), NotNull(user), NotNull(item) ->
      security.Does(user).Have(MyPermission).On(item)
  | _ -> true

活动模式的声明是:

let (|NotNull|_|) a = if a <> null then Some(a) else None

然而,即使这并不比你拥有的东西更直接。我想处理null值只是痛苦:-)。这个article by Ian Griffiths有一些相关的想法,但同样,它们都没有真正解决问题。

答案 1 :(得分:1)

我会对Tomas的答案做一点调整:使用Object.ReferenceEquals进行空检查而不是=。它更快,更重要的是,您不必使用AllowNullLiteral属性标记在F#中声明的类型。我通常为将从C#使用的F#代码定义一个Interop模块。这隔离了空处理,并且由于它不需要使用[<AllowNullLiteral>],因此您可以忽略F#中的null并且仅在与C#(即您的公共接口)交互时处理它。这是我使用的模块(从this answer复制):

[<AutoOpen>]
module Interop =

    let inline (===) a b = obj.ReferenceEquals(a, b)
    let inline (<=>) a b = not (a === b)
    let inline isNull value = value === null
    let inline nil<'T> = Unchecked.defaultof<'T>
    let inline safeUnbox value = if isNull value then nil else unbox value
    let (|Null|_|) value = if isNull value then Some() else None

type Foo() = class end

type Test() =
    member this.AcceptFoo(foo:Foo) = //passed from C#
        if isNull foo then nullArg "foo"
        else ...

    member this.AcceptFoo2(foo:Foo) = //passed from C#
        match foo with
        | Null -> nullArg "foo"
        | _ -> ...

    member this.AcceptBoxedFoo(boxedFoo:obj) =
        let foo : Foo = safeUnbox boxedFoo
        ...

    member this.ReturnFoo() : Foo = //returning to C#
        if (test) then new Foo()
        else nil
fssnip.net上的

Snippet