使用Newtonsoft JSON解释ObjectCreationHandling?

我正在追踪一个错误,我注意到Newtonsoft JSON会将项目附加到已在默认构造函数中初始化的List 。 我做了一些挖掘并与C#chat上的一些人讨论过,我们注意到这种行为并不适用于所有其他集合类型。

https://dotnetfiddle.net/ikNyiT

 using System; using Newtonsoft.Json; using System.Collections.Generic; using System.Collections.ObjectModel; public class TestClass { public Collection Collection = new Collection(new [] { "ABC", "DEF" }); public List List = new List(new [] { "ABC", "DEF" }); public ReadOnlyCollection ReadOnlyCollection = new ReadOnlyCollection(new [] { "ABC", "DEF" }); } public class Program { public static void Main() { var serialized = @"{ Collection: [ 'Goodbye', 'AOL' ], List: [ 'Goodbye', 'AOL' ], ReadOnlyCollection: [ 'Goodbye', 'AOL' ] }"; var testObj = JsonConvert.DeserializeObject(serialized); Console.WriteLine("testObj.Collection: " + string.Join(",", testObj.Collection)); Console.WriteLine("testObj.List: " + string.Join(",", testObj.List)); Console.WriteLine("testObj.ReadOnlyCollection: " + string.Join(",", testObj.ReadOnlyCollection)); } } 

输出:

 testObj.Collection: ABC,DEF testObj.List: ABC,DEF,Goodbye,AOL testObj.ReadOnlyCollection: Goodbye,AOL 

如您所见, Collection属性不受反序列化的影响,将附加List并替换ReadOnlyCollection 。 这是预期的行为吗? 是什么原因?

它基本归结为类型实例化和ObjectCreationHandling设置。 ObjectCreationHandling有三种设置

自动0重新使用现有对象,在需要时创建新对象。
重用1仅重用现有对象。
替换2始终创建新对象。

默认值为auto ( 第44行 )。

只有在一系列检查确定当前类型是否具有null的TypeInitializer之后,才会覆盖Auto。 此时它会检查是否存在无参数构造函数。

///
///创建一个工厂函数,可用于创建由…描述的JsonConverter的实例
///参数类型。
///然后可以使用返回的函数来调用转换器的默认ctor或any
///通过对象数组的参数化构造函数。
///

基本上它就像这样(它看起来像6行中的大约1500行代码)。

 ObjectCreationHandling och = ObjectCreationHandling.Auto; if( typeInitializer == null ) { if( parameterlessConstructor ) { och = ObjectCreationHandling.Reuse; } else { och = ObjectCreationHandling.Replace; } } 

此设置是JsonSerializerSettings的一部分,它们在DeserializeObject的访问者模式构造函数内部组成。 如上所示,每个设置具有不同的function。

回到List,Collection和ReadOnlyCollection,我们将查看每个条件语句的集合。

名单

testObj.List.GetType().TypeInitializer == null为false。 因此, List接收默认的ObjectCreationHandling.Auto,并且在反序列化期间使用testObj实例的实例化List,以及使用serialized字符串实例化的新List。

 testObj.List: ABC,DEF,Goodbye,AOL 

采集

testObj.Collection.GetType().TypeInitializer == null为true表示没有可用的reflection类型初始值设定项,因此我们转到下一个检查是否存在无参数构造函数的条件。 testObj.Collection.GetType().GetConstructor(Type.EmptyTypes) == null为false。 结果Collection收到ObjectCreationHandling.Reuse的值( 仅重用现有对象 )。 Collection的实例化实例用于testObj,但serialized字符串无法实例化。

 testObj.Collection: ABC,DEF 

ReadOnlyCollection

testObj.ReadOnlyCollection.GetType().TypeInitializer == null为true表示没有可用的reflection类型初始值设定项,因此我们转到下一个检查是否存在无参数构造函数的条件。 testObj.ReadOnlyCollection.GetType().GetConstructor(Type.EmptyTypes) == null也是如此。 因此,ReadOnlyCollection接收ObjectCreationHandling.Replace的值( 始终创建新对象 )。 仅使用serialized字符串中的实例化值。

 testObj.ReadOnlyCollection: Goodbye,AOL 

虽然这已经解决了,但我想把这个答案最初发布到一个重复的问题,但是这个问题已经关闭了,所以我在这里发布我的答案,因为它包含了一些内部的看法。

因为Json.NET是开源的,所以我们可以幸运地找到它的根源:-)。

如果检查Json.NET源代码,可以找到处理反序列化的类JsonSerializerInternalReader ( 此处为完整源代码 )。 这个类有一个方法SetPropertyValue ,它在新创建的对象上设置反序列化的值(代码缩写):

 private bool SetPropertyValue(JsonProperty property, ..., object target) { ... if (CalculatePropertyDetails( property, ..., out useExistingValue, ... )) { return false; } ... if (propertyConverter != null && propertyConverter.CanRead) { ... } else { value = CreateValueInternal( ..., (useExistingValue) ? currentValue : null); } if ((!useExistingValue || value != currentValue) && ShouldSetPropertyValue(property, value)) { property.ValueProvider.SetValue(target, value); ... return true; } return useExistingValue; } 

如您所见,有一个布尔标志useExistingValue ,用于确定是否重用或替换现有值。

CalculatePropertyDetails方法内部是以下代码段:

  if ((objectCreationHandling != ObjectCreationHandling.Replace) && (tokenType == JsonToken.StartArray || tokenType == JsonToken.StartObject) && property.Readable) { currentValue = property.ValueProvider.GetValue(target); gottenCurrentValue = true; if (currentValue != null) { ... useExistingValue = ( !propertyContract.IsReadOnlyOrFixedSize && !propertyContract.UnderlyingType.IsValueType()); } } 

对于List底层集合, IsReadOnlyOrFixedSize返回false并且IsValueType()返回false – 因此将重用基础现有值。

对于ArrayIsValueType()也是false ,但是由于显而易见的原因, IsReadOnlyOrFixedSizetrue ,因此useExistingValue标志设置为false并且SetPropertyValue方法中的CreateValueInternal调用接收null引用,该引用是不重用现有值的指示符,但要创建一个新的,然后在新实例上设置。

如前所述,可以使用ObjectCreationHandling.Replace更改此行为,因为在CalculatePropertyDetails方法中设置useExistingValue之前会对此进行检查。