简体   繁体   English

我是否应该从C#调用中保护我的F#代码

[英]Should I null-protect my F# code from C# calls

I am writing a library in F#, with some interfaces and base classes that are publicly visible. 我正在用F#编写一个库,其中一些接口和基类是公开可见的。 Generally, I avoid specifying [<AllowNullLiteral>] on my custom types as this complicates the validation logic in my F# code (see this nice post for goods and bads of null handing in F# to get a picture ), and also, F# does not initially allow null for F# types. 一般来说,我避免在我的自定义类型上指定[<AllowNullLiteral>] ,因为这会使我的F#代码中的验证逻辑变得复杂(请参阅这篇关于货物的好帖子以及F#中的空值处理以获取图片 ),而且,F#不会最初允许F#类型为null So, I validate for nulls only for types that accept the null value as valid. 因此,我仅对接受null值为有效的类型验证null

However, an issues arises when my library is used from another .NET language, such as C#. 但是,当我的库使用其他.NET语言(例如C#)时会出现问题。 More particularly, I worry how should I implement methods that accept F#-declared interfaces, when called by C# code. 更具体地说,我担心在C#代码调用时,我应该如何实现接受F#-declared接口的方法。 The interface types are nullable in C#, and I suspect that there will not be an issue for the C# code to pass a null to my F# method. 接口类型在C#中可以为空,我怀疑C#代码不会将null传递给我的F#方法。

I fear that the caller would crash and burn with a NPE, and the problem is that I am not even allowed to properly handle that in the F# code -- say throw an ArgumentNullException -- because the respective interface lack the AllowNullLiteral attribute. 我担心调用者会崩溃并使用NPE进行刻录,问题是我甚至不允许在F#代码中正确处理它 - 比如抛出一个ArgumentNullException - 因为相应的接口缺少AllowNullLiteral属性。 I fear that I would have to use the attribute and add the related null-checking logic in my F# code to eventually prevent such a disaster. 我担心我必须使用该属性并在我的F#代码中添加相关的空检查逻辑,以最终防止这种灾难。

Are my fears reasonable? 我的恐惧是否合理? I am a little confused as I initially attempt to stick to the good F# practices and avoid null as much as possible. 我有点困惑,因为我最初试图坚持良好的F#做法,并尽可能避免null How does this change if among my goals is to allow C# code to subclass and implement the interfaces I created in F#? 如果我的目标之一是允许C#代码继承并实现我在F#中创建的接口,这会如何改变? Do I have to allow nulls for all non-value types coming from my F# code if they are public and can be accessed from any CLR language? 如果它们是公共的并且可以从任何CLR语言访问,我是否必须允许来自我的F#代码的所有非值类型的空值? Is there a best practice or a good advice to follow? 是否有最佳实践或良好建议?

There are two basic approaches you can take: 您可以采取两种基本方法:

  1. Document in your API design that passing null to your library is not allowed, and that the calling code is responsible for ensuring that your library never receives a null . 您的API设计中的文档不允许将null传递给您的库,并且调用代码负责确保您的库永远不会收到null Then ignore the problem, and when your code throws NullReferenceException s and users complain about it, point them to the documentation. 然后忽略该问题,当您的代码抛出NullReferenceException并且用户抱怨它时,请将它们指向文档。

  2. Assume that the input your library receives from "outside" cannot be trusted, and put a validation layer around the "outside-facing" edge of your library. 假设您的库从“外部”接收的输入不可信,并在库的“面向外”边缘周围放置一个验证层。 That validation layer would be responsible for checking for null and throwing ArgumentNullException s. 该验证层将负责检查null并抛出ArgumentNullException (And pointing to the documentation that says "No nulls allowed" in the exception message). (并指出在异常消息中说“不允许空值”的文档)。

As you can probably guess, I favor approach #2, even though it takes more time. 正如你可能猜到的那样,我赞成方法#2,即使它需要更多的时间。 But you can usually make a single function, used everywhere, to do that for you: 但是你通常可以制作一个在任何地方使用的功能,为你做到这一点:

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

Or, if you have a more complicated data structure that you have to inspect for internal nulls, then treat it like a validation problem in HTML forms. 或者,如果您有一个更复杂的数据结构,您必须检查内部空值,然后将其视为HTML表单中的验证问题。 Your validation functions will either throw an exception, or else they will return valid data structures. 您的验证函数将抛出异常,否则它们将返回有效的数据结构。 So the rest of your library can ignore nulls completely, and be written in a nice, simple, idiomatic-F# way. 所以你的库的其余部分可以完全忽略空值,并用一个漂亮,简单,惯用的F#方式编写。 And your validation functions can handle the interface between your domain functions and the untrusted "outside world", just as you would with user input in an HTML form. 您的验证功能可以处理域功能与不受信任的“外部世界”之间的接口,就像在HTML表单中使用用户输入一样。

Update: See also the advice given near the bottom of https://fsharpforfunandprofit.com/posts/the-option-type/ (in the "F# and null" section), where Scott Wlaschin writes, "As a general rule, nulls are never created in "pure" F#, but only by interacting with the .NET libraries or other external systems. [...] In these cases, it is good practice to immediately check for nulls and convert them into an option type!" 更新:另请参阅https://fsharpforfunandprofit.com/posts/the-option-type/ (在“F#和null”部分)底部附近给出的建议,其中Scott Wlaschin写道:“作为一般规则,nulls永远不会在“纯粹的”F#中创建,而只能通过与.NET库或其他外部系统交互来创建。[...]在这些情况下,最好立即检查空值并将它们转换为选项类型!“ Your library code, which expects to get data from other .NET libraries, would be in a similar situation. 您希望从其他.NET库获取数据的库代码也会出现类似情况。 If you want to allow nulls, you'd convert them to the None value of an Option type. 如果要允许空值,则将它们转换为Option类型的None值。 If you want to disallow them and throw ArgumentNullException s when you get passed a null, you'd also do that at the boundaries of your library. 如果你想要禁用它们并在传递null时抛出ArgumentNullException ,你也可以在库的边界处这样做。

Based on @rmunn's advice I ended up creating a simple null2option function: 根据@ rmunn的建议,我最终创建了一个简单的null2option函数:

let null2option arg = if obj.ReferenceEquals(arg, null) then None else Some arg

It solved most of my cases alone. 它解决了我的大部分案件。 If I expect a null argument to be coming for the calling code I would simply use this idioms: 如果我期望调用代码的null参数,我会简单地使用这个习语:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM