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.
精彩评论