C# 我应该空值保护我的F代码不受C调用的影响吗

C# 我应该空值保护我的F代码不受C调用的影响吗,c#,f#,null,nullreferenceexception,C#,F#,Null,Nullreferenceexception,我正在用F#编写一个库,其中包含一些公开可见的接口和基类。通常,我避免指定[),而且F#最初不允许F#类型使用null。因此,我只对接受null值为有效值的类型验证null 但是,当我的库从另一种.NET语言(如C#)使用时,会出现一个问题。更具体地说,我担心当C#代码调用时,如何实现接受F声明接口的方法。C#代码中的接口类型可以为null,我怀疑C#代码将null传递给我的F不会有问题#方法 我担心调用方会崩溃并被NPE烧掉,问题是我甚至不被允许在F#code中正确处理这个问题——比如抛出Ar

我正在用F#编写一个库,其中包含一些公开可见的接口和基类。通常,我避免指定
[),而且F#最初不允许F#类型使用
null
。因此,我只对接受
null
值为有效值的类型验证null

但是,当我的库从另一种.NET语言(如C#)使用时,会出现一个问题。更具体地说,我担心当C#代码调用时,如何实现接受F声明接口的方法。C#代码中的接口类型可以为null,我怀疑C#代码将
null
传递给我的F不会有问题#方法

我担心调用方会崩溃并被NPE烧掉,问题是我甚至不被允许在F#code中正确处理这个问题——比如抛出
ArgumentNullException
——因为相应的接口缺少
AllowNullLiteral
属性。我担心我必须使用该属性并添加相关的nu我将在我的F#代码中检查逻辑,以最终防止这样的灾难


我的担心合理吗?当我最初试图坚持良好的F#实践并尽可能避免
null
时,我有点困惑。如果我的目标之一是允许C#code子类化并实现我在F#中创建的接口,那么这种情况会发生什么变化?如果我的F#code中的所有非值类型都允许null它们是公开的,可以从任何CLR语言访问?是否有最佳实践或良好建议可供遵循?

您可以采取两种基本方法:

  • 在API设计中记录不允许将
    null
    传递到库,并且调用代码负责确保库从不接收
    null
    。然后忽略该问题,当代码抛出
    NullReferenceException
    且用户抱怨时,将他们指向该文档反倾销

  • 假设您的库从“外部”接收的输入不可信,并在库的“外部”边缘放置一个验证层。该验证层将负责检查
    null
    并抛出
    ArgumentNullException
    s。(并指向异常消息中的“不允许为空”)

  • 正如你可能猜到的那样,我喜欢方法2,尽管它需要更多的时间。但是你通常可以制作一个函数,在任何地方都可以使用,来为你做到这一点:

    let nullArg name message =
        raise new System.ArgumentNullException(name, message)
    
    let guardAgainstNull value name =
        if isNull value then nullArg name "Nulls not allowed in Foo library functions"
    
    let libraryFunc a b c =
        guardAgainstNull a nameof(a)
        guardAgainstNull b nameof(b)
        guardAgainstNull c nameof(c)
        // Do your function's work here
    
    或者,如果您有一个更复杂的数据结构,您必须检查其内部空值,然后将其视为HTML表单中的验证问题。您的验证函数将抛出异常,否则它们将返回有效的数据结构。因此,库的其余部分可以完全忽略空值,并以一种漂亮、简单的,你的验证函数可以处理域函数和不受信任的“外部世界”之间的接口,就像你处理HTML表单中的用户输入一样


    更新:另请参见(在“F#和null”部分)底部附近给出的建议,Scott Wlaschin在其中写道,“一般来说,在“纯”F#中永远不会创建null,而只能通过与.NET库或其他外部系统交互。[…]在这些情况下,最好立即检查空值并将其转换为选项类型!"您希望从其他.NET库获取数据的库代码也会处于类似的情况。如果您希望允许空值,则应将其转换为
    选项
    类型的
    None
    值。如果您希望禁用它们并在传递空值时抛出
    ArgumentNullException
    s,也应在boun处执行此操作您库的daries。

    根据@rmunn的建议,我最终创建了一个简单的
    null2选项
    函数:

    let null2option arg = if obj.ReferenceEquals(arg, null) then None else Some arg
    
    它单独解决了我的大多数情况。如果我预期调用代码会出现空参数,我只会使用以下习惯用法:

    match null2option arg with | None -> nullArg "arg" "Message" | _ -> ()
    

    为了支持我更喜欢的#2,我假设可能导致NPE的相关类型将用
    []注释
    此外,为了防止来自F#编译器的警告/错误,
    isNull
    函数不要求您使用
    AllowNullLiteral
    属性,因此我建议不要使用该属性。C#无论如何都会忽略它,因此没有它不会保护您从调用函数的C#代码中获取null。但是如果如果您不使用
    AllowNullLiteral
    ,那么至少任何调用库函数的F#code都将被禁止传递空值。除非您有特殊需要使用空值,否则最好避免使用
    AllowNullLiteral
    属性,如果您必须保护自己不受C#code的影响,请调用
    isNull
    函数。如果您需要从F#代码中表达“这可能不存在”方面,您应该(正如您已经知道的)使用
    Option
    s而不是null。由于需要调用库的C#code无法理解
    选项的类型,请在输入时编写使用
    Option.ofObj
    的精简C#wrapper。这将使任何null变为
    None
    ,而将任何非null对象变为
    Some(无论什么)
    ,在某种程度上,F编译器可以为您进行类型检查。上面所说的“精简C包装器”,我指的是“用F编写的精简函数,仅用于从C代码调用”。他们唯一的工作就是在输入时调用
    Option.ofObj
    ,然后将生成的
    Option
    s传递给完成所有工作的主F#函数。我想举一个很好的例子来说明我的意思。有一个函数打算从F#调用(并且从C#IDE中隐藏),还有两个函数打算从C#调用(如果从F#调用,会发出警告)。太好了!这是我实际上正在寻找的解决方案,但不幸的是