Record Equals或GetHashCode抛出NullReferenceException

时间:2013-12-20 17:18:42

标签: f# nullreferenceexception .net-4.5

我有许多记录如下:

[<DataContract>]
type Rec1 = {
    [<DataMember>] mutable field1 : int;
    [<DataMember>] mutable field2 : string;
}

[<DataContact>]
type Rec2 = { 
    [<DataMember>] mutable field3 : Rec1;
    [<DataMember>] mutable field4 : int;
}

我使用DataContactJsonSerializer将JSON反序列化为此结构。这是一个有效的JSON值:

{ "field3": null, "field4": 1 }

这意味着在运行时,field3null/Unchecked.defaultOf<_>。在Visual Studio 2010中,此测试工作正常:

(deserialize<Rec2> "{ field3: null, field4: 1 }") = { field3 = Unchecked.defaultOf<_>; field4 = 1 } //true

在Visual Studio 2013中,相同的代码会抛出NullReferenceException

at Rec2.Equals(Rec2 obj) 

偷看ILSpy中的代码,我看到这是生成的:

if(this != null)
{
    return obj != null && this.field3.Equals(obj.field3) && this.field4.Equals(obj.field4);
}
return obj == null;

所以问题是编译器假定field3永远不会为空,而DataContractJsonSerializer将值设置为null则不是这种情况。我已尝试将AllowNullLiteral属性应用于Rec1,但不允许F#记录将该属性应用于它们。如何告诉编译器字段可以是null还是重新构造我的类型以允许它工作?

3 个答案:

答案 0 :(得分:8)

F#正在生成类型,假设它只能在F#中使用,null是不可能的。因为在该元素上不允许[<AllowNullLiteral>],我认为没有办法控制代码gen,因此它会解释这种可能性。我可以想到两种方法来解决这个问题

  1. Rec2上实现自定义相等,而不是默认的结构相等。这意味着您必须自己编写相等方法,这是不幸的,但它应该允许您考虑null
  2. 的可能性
  3. 更改代码以生成struct个实例,而不是class。这消除了null在任何CLR用例中的可能性。这将要求您从记录定义移动到完整类型

答案 1 :(得分:0)

我在一个项目上遇到了这个问题。在我看来,这是EqualGetHashCode生成的代码中的一个错误。

对我来说,解决方法是在反序列化后立即替换空值。请参阅我已添加到示例代码中的removeNulls函数:

[<DataContract>]
type Rec1 =
    {
        [<DataMember>] mutable field1 : int
        [<DataMember>] mutable field2 : string
    }
    static member empty = { field1 = 0; field2 = String.Empty }

[<DataContract>]
type Rec2 =
    { 
        [<DataMember>] mutable field3 : Rec1;
        [<DataMember>] mutable field4 : int;
    }
    static member removeNulls (rec2 : Rec2) =
        { rec2 with
            field3 = if Object.ReferenceEquals(rec2.field3, null)
                         then Rec1.empty
                         else rec2.field3
        }

      :
      :         

let rec2 = deserialize<Rec2> json
let rec2' = Rec2.removeNulls rec2

这解决了整个Rec2实例使用过程中的问题 - 不仅仅是在框架恰好调用EqualGetHashCode时。否则,每当您在代码中访问field3时,都必须检查{{1}}是否为空。

答案 2 :(得分:-1)

问题似乎是您认为nullRec1类型的有效值,但事实并非如此。该值在那里无效,因此您将垃圾数据提供给F#代码。

如果您将null提供给任何不期望的代码,您就会得到类似的行为。例如:

2::1::Unchecked.defaultof<_>

打印[2]这只是废话。垃圾进去,垃圾出来......