Record Equals or GetHashCode throws NullReferenceException
.net-4.5, f#, nullreferenceexception
Solution
F# is generating the types assuming it will only ever be used from F# where `null` isn't possible. Because `[<AllowNullLiteral>]` isn't allowed on that element I don't think there is a way to control code gen such that it will account for this possibility. I can think of 2 approaches to fixing this
- Implement custom equality on the `Rec2` instead of the default structural equality. This means you have to write the equality method yourself which is unfortunate but it should allow you to account for the possibility of `null`
- Change the code to generate `struct` instances instead of `class`. This eliminates the possibility of `null` in any CLR use case. This will require you to move from a record definition to a full type though
Problem
I have a number of records that look like this: ``` [<DataContract>] type Rec1 = { [<DataMember>] mutable field1 : int; [<DataMember>] mutable field2 : string; } [<DataContact>] type Rec2 = { [<DataMember>] mutable field3 : Rec1; [<DataMember>] mutable field4 : int; } ``` I use `DataContactJsonSerializer` to deserialize JSON into this structure. This is a valid JSON value: ``` { "field3": null, "field4": 1 } ``` Which means at runtime, `field3` is `null/Unchecked.defaultOf<_>`. In Visual Studio 2010, this test works fine: ``` (deserialize<Rec2> "{ field3: null, field4: 1 }") = { field3 = Unchecked.defaultOf<_>; field4 = 1 } //true ``` In Visual Studio 2013, the same code throws a `NullReferenceException`: ``` at Rec2.Equals(Rec2 obj) ``` Peeking at the code in ILSpy, I see this is generated: ``` if(this != null) { return obj != null && this.field3.Equals(obj.field3) && this.field4.Equals(obj.field4); } return obj == null; ``` So the issue is the compiler assumes that `field3` is never null which isn't the case since `DataContractJsonSerializer` sets the value to `null`. I've tried applying the `AllowNullLiteral`attribute to `Rec1` but F# records aren't allowed to have that attribute applied to them. How can I either tell the compiler that the fields can be `null` or re-structure my types to allow this to work?