开发者

NullReferenceException in F#

When I step through the following code, report on the second line is nu开发者_如何学Goll. However, the third line generates a NullReferenceException.

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

Why is that, and what can I do to avoid it? Thanks!

Added later:

Here's the line that calls this.setTaggedResearchReportList:

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

Here's the getMostRecentTaggedResearchReportForSecurityId method:

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 
        )


This isn't an answer per se, but to add to the discussion of null handling, particularly when interop-ing with C# code: I like to avoid using the [<AllowNullLiteral>] attribute and define a module such as the following to isolate the use of null in F# code.

[<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

In general, keep these checks as close to the interface of your API as possible and you can typically forget about null within F#, due to preserving the compiler's null checks.


Your TaggedResearchReportUIVO type is evidently defined in F#, and doesn't allow null as a proper value. Therefore, the compiler will prevent you from using the literal null as a value of that type; however, some other code is going behind the compiler's back and sticking a null value in there. To work around the immediate issue, you can try comparing against Unchecked.defaultof<TaggedResearchReportUIVO> instead of null.

However, it's probably worth assessing whether you should be making some more significant changes to avoid this type of issue in the first place. For instance, if it makes sense to have null as a proper value of type TaggedResearchReportUIVO, then you could add the [<AllowNullLiteral>] attribute to the definition of that type. Alternatively, if it really doesn't make sense to use null as a proper value, then you need to investigate the code which is generating the problematic value.

As an aside, there are other parts of your code that can be cleaned up considerably. For example, consider changing

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

to

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


Since taggedResearchReport is an option type, you want to use pattern matching for your logic here:

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"

Update

I'm a bit lost in the additional code you added, but there is definitely some funky stuff going on with the way you are using option types. Use pattern matching instead of IsSome and Option.get. e.g. in your lambda expression should look more like this:

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

I'm not sure if that will solve your problems, but it's a start.


As others pointed out, the problem is that the deserializer may return null, but since you're working with F# types, you cannot directly check whether the value is null!

I think the best approach is to get rid of the null value coming from some .NET libraries as early as possible, so that you can keep the rest of the code clean. Here is how I think it could be fixed:

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 )

Then you can safely use pattern matching to work with the value, because it will never be null:

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

I wouldn't probably use AllowNullLiteral because the only place where you get this problem is when getting the result from JSON library. Once you solve the problem there, you can safely use the value everywhere else in F# code (without checking for null).


If report is null, you cannot call report.Equals, so the code is useless. Change it to report = null. Also, defining taggedResearchReport as option and checking it for null seems to be a wrong use-case.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜