我有许多记录如下:
[<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 }
这意味着在运行时,field3
为null/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
还是重新构造我的类型以允许它工作?
答案 0 :(得分:8)
F#正在生成类型,假设它只能在F#中使用,null
是不可能的。因为在该元素上不允许[<AllowNullLiteral>]
,我认为没有办法控制代码gen,因此它会解释这种可能性。我可以想到两种方法来解决这个问题
Rec2
上实现自定义相等,而不是默认的结构相等。这意味着您必须自己编写相等方法,这是不幸的,但它应该允许您考虑null
struct
个实例,而不是class
。这消除了null
在任何CLR用例中的可能性。这将要求您从记录定义移动到完整类型答案 1 :(得分:0)
我在一个项目上遇到了这个问题。在我看来,这是Equal
和GetHashCode
生成的代码中的一个错误。
对我来说,解决方法是在反序列化后立即替换空值。请参阅我已添加到示例代码中的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实例使用过程中的问题 - 不仅仅是在框架恰好调用Equal
或GetHashCode
时。否则,每当您在代码中访问field3
时,都必须检查{{1}}是否为空。
答案 2 :(得分:-1)
问题似乎是您认为null
是Rec1
类型的有效值,但事实并非如此。该值在那里无效,因此您将垃圾数据提供给F#代码。
如果您将null
提供给任何不期望的代码,您就会得到类似的行为。例如:
2::1::Unchecked.defaultof<_>
打印[2]
这只是废话。垃圾进去,垃圾出来......