【问题标题】:NullReferenceException in F#F# 中的 NullReferenceException
【发布时间】:2011-07-24 15:50:38
【问题描述】:

当我单步执行以下代码时,第二行的report 为空。 但是,第三行会生成 NullReferenceException。

member this.setTaggedResearchReportList (index : int) (taggedResearchReport : TaggedResearchReportUIVO option) =
    let report = Option.get(taggedResearchReport)
    if not(report.Equals(null)) then
        // do some stuff here

为什么会这样,我能做些什么来避免它?谢谢!

稍后添加:

这是调用this.setTaggedResearchReportList的行:

getMostRecentTaggedResearchReportForSecurityId (item.id) (new Action<_>(this.setTaggedResearchReportList 0))

这是getMostRecentTaggedResearchReportForSecurityId 方法:

let getMostRecentTaggedResearchReportForSecurityId (securityId : int) (callbackUI : Action<_>) =
    getSingleRPCResult<JSONSingleResult<TaggedResearchReportUIVO>, TaggedResearchReportUIVO>
        "TaggedResearchReportRPC"  
        "getMostRecentResearchReportForSecurityId" 
        (sprintf "%i" securityId)
        callbackUI
        (fun (x : option<JSONSingleResult<TaggedResearchReportUIVO>>) ->
            match x.IsSome with
                | true -> Some(Option.get(x).result)
                | false -> None 
        )

【问题讨论】:

    标签: null f# nullreferenceexception


    【解决方案1】:

    这本身不是一个答案,而是为了增加对空值处理的讨论,特别是在与 C# 代码互操作时:我喜欢避免使用 [&lt;AllowNullLiteral&gt;] 属性并定义一个如下所示的模块在 F# 代码中隔离 null 的使用。

    [<AutoOpen>]
    module Interop =
    
        let inline isNull value = System.Object.ReferenceEquals(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
    

    一般而言,这些检查应尽可能靠近您的 API 接口,由于保留了编译器的空检查,您通常可以忘记 F# 中的 null

    【讨论】:

    • 谢谢!希望其他人觉得它有帮助。
    • 3 年多之后,是的……它很有用。干杯!
    • 请注意:在 F# 4.0 中,我们现在有 isNull operator
    【解决方案2】:

    您的 TaggedResearchReportUIVO 类型显然是在 F# 中定义的,并且不允许将 null 作为正确的值。因此,编译器将阻止您使用文字 null 作为该类型的值;但是,其他一些代码在编译器的背后并在其中粘贴了一个空值。要解决眼前的问题,您可以尝试与 Unchecked.defaultof&lt;TaggedResearchReportUIVO&gt; 而不是 null 进行比较。

    但是,您可能值得评估一下您是否应该首先进行一些更重大的更改来避免此类问题。例如,如果将null 作为TaggedResearchReportUIVO 类型的正确值是有意义的,那么您可以将[&lt;AllowNullLiteral&gt;] 属性添加到该类型的定义中。或者,如果使用 null 作为正确的值确实没有意义,那么您需要调查生成有问题的值的代码。

    顺便说一句,您的代码的其他部分可以进行大量清理。例如,考虑改变

    fun (x : option<JSONSingleResult<TaggedResearchReportUIVO>>) -> 
        match x.IsSome with
        | true -> Some(Option.get(x).result)
        | false -> None
    

    Option.map (fun (jsr : JSONSingleResult<_>) -> jsr.result)
    

    【讨论】:

    • 谢谢! Unchecked.defaultof&lt;TaggedResearchReportUIVO&gt; 现在是一个很好的解决方案,因此我可以快速修复此错误。我还尝试了[&lt;AllowNullLiteral&gt;] 属性,不幸的是它不起作用(似乎我的cmets 对其他问题之一)。我将研究您提到的代码重构。我对 F# 相当陌生,并且正在使用这种模式,该模式是由其他同样是 F# 新手的人实现的......所以我认为还有很大的改进空间。
    【解决方案3】:

    由于taggedResearchReportoption 类型,您希望在此处为您的逻辑使用模式匹配:

    member this.setTaggedResearchReportList (index : int) (taggedResearchReport : TaggedResearchReportUIVO option) =
       match taggedResearchReport with
       | Some(report) -> //do some stuff when we have "something"
       | None -> //do some different stuff when we have "nothing"
    

    更新

    我对您添加的附加代码有点迷茫,但是您使用选项类型的方式肯定有一些时髦的东西。使用模式匹配代替IsSomeOption.get。例如在你的 lambda 表达式中应该看起来更像这样:

    (fun (x : option<JSONSingleResult<TaggedResearchReportUIVO>>) ->
                match x with
                    | Some(value) -> Some(value.result)
                    | None -> None 
            )
    

    我不确定这是否能解决您的问题,但这是一个开始。

    【讨论】:

    • 所以我试过了......问题是taggedResearchReportSome(null),所以它永远不会到达None 部分。我还尝试在顶部放置 | Some(null) -&gt; // stuff 匹配,然后得到 The type 'TaggedResearchReportUIVO' does not have 'null' as a proper value. Murgh。
    • @Mike - 你能显示你调用setTaggedResearchReportList的代码吗?你的 taggedResearchReport 参数一定是有一些时髦的地方。
    • Yikes -- 尝试将AllowNullLiteral 属性应用于TaggedResearchReportUIVO 的定义,而不是在option 中包装该类型的值(我不太了解RPC,但听起来像你需要处理null)。
    • @Mike - 请不要气馁。在实践中,我发现 F# 中 nulloption 类型的可用性几乎从来都不是问题。这里不好的原因是因为您试图将两者混合在一起。上面提到的三种类型中,TaggedResearchReportUIVO是哪种类型?
    • (如果是record类型或者struct类型,那么应该可以转成可以接受NullLiteral属性的类类型(msdn.microsoft.com/en-us/library/dd233205.aspx)。如果是union类型,那么你需要按照你的建议“返回一个空的TaggedResearchReportUIVO”。)
    【解决方案4】:

    正如其他人指出的那样,问题在于反序列化器可能返回null,但由于您使用的是F#类型,因此您无法直接检查该值是否为null

    我认为最好的方法是尽早摆脱来自某些 .NET 库的null 值,这样您就可以保持其余代码干净。以下是我认为可以修复的方法:

    let getMostRecentTaggedResearchReportForSecurityId 
            (securityId : int) (callbackUI : Action<_>) =
        getSingleRPCResult< JSONSingleResult<TaggedResearchReportUIVO>, 
                            TaggedResearchReportUIVO >
            "TaggedResearchReportRPC"  
            "getMostRecentResearchReportForSecurityId" 
            (sprintf "%i" securityId)
            callbackUI
            (fun (x : option<JSONSingleResult<TaggedResearchReportUIVO>>) ->
                // Note: Return 'Some' only when the result is 'Some' and the 'result'
                // value carried inside the discriminated union is not 'null'
                match x with
                | Some(res) when 
                      // Make sure that there are no dangerous values (I suppose 
                      // JSONSingleResult is imported from .NET and 
                      // TaggedResearchReportUIVO is your F# type.
                      res <> null && res.result <> Unchecked.defaultOf<_> -> 
                    Some(res.result)
                | _ -> None )
    

    然后您可以安全地使用模式匹配来处理该值,因为它永远不会是null

    member this.setTaggedResearchReportList 
            (index : int) (taggedResearchReport : TaggedResearchReportUIVO option) =
        match taggedResearchReport with 
        | Some(report) ->
            // do some stuff here
        | _ -> () // do nothing here
    

    我可能不会使用AllowNullLiteral,因为您遇到此问题的唯一地方是从 JSON 库获取结果时。一旦在那里解决了问题,您就可以在 F# 代码中的其他任何地方安全地使用该值(无需检查 null)。

    【讨论】:

      【解决方案5】:

      如果report为null,则不能调用report.Equals,所以代码没用。将其更改为report = null。此外,将taggedResearchReport 定义为option 并检查null 似乎是一个错误的用例。

      【讨论】:

      • 我已经尝试过了,但是我得到了一个The type 'TaggedResearchReportUIVO' does not have 'null' as a proper value 编译错误。这是一个非常奇怪的问题...我在执行Option.get 之前尝试过检查null,但这也不好。
      • @Mike Cialowicz:默认情况下,在 F# 中创建的类型不允许“将 null 作为适当的值”(更喜欢 option 类型明确表示“无”),但这仅在编译时强制执行时间(例如与= 比较),而Option.get 为您提供您在运行时观察到的null 值。使用Option.get 是你几乎不想做的事情,总是对option 类型进行模式匹配。
      • @Mike 所以你应该使用模式匹配而不是空值检查。
      猜你喜欢
      • 2010-11-05
      • 2013-03-12
      • 2016-09-20
      • 2013-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-06
      相关资源
      最近更新 更多