C# 我应该空值保护我的F代码不受C调用的影响吗
我正在用F#编写一个库,其中包含一些公开可见的接口和基类。通常,我避免指定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#类型使用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#调用,会发出警告)。太好了!这是我实际上正在寻找的解决方案,但不幸的是