Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Record Equals or GetHashCode throws NullReferenceException

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 AllowNullLiteralattribute 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?

like image 976
Wesley Wiser Avatar asked Dec 20 '13 17:12

Wesley Wiser


1 Answers

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

  1. 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
  2. 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
like image 69
JaredPar Avatar answered Nov 15 '22 07:11

JaredPar