我正在追踪一个错误,我注意到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<string> Collection = new Collection<string>(new [] { "ABC", "DEF" });
public List<string> List = new List<string>(new [] { "ABC", "DEF" });
public ReadOnlyCollection<string> ReadOnlyCollection = new ReadOnlyCollection<string>(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<TestClass>(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<>
并被{{1}}替换。这是预期的行为吗?是什么原因?
答案 0 :(得分:9)
它基本归结为类型实例化和ObjectCreationHandling
设置。 ObjectCreationHandling
自动0重新使用现有对象,在需要时创建新对象 重用1仅重用现有对象 替换2始终创建新对象。
默认值为auto
(Line 44)。
自动仅在经过一系列检查后被覆盖,这些检查确定当前类型的TypeInitializer
是否为空。此时它会检查是否存在无参数构造函数。
/// ///创建一个工厂函数,可用于创建由中描述的JsonConverter实例 ///参数类型。
///然后可以使用返回的函数调用转换器的默认ctor或任何
///通过对象数组的参数化构造函数 ///
基本上它就像这样(它看起来像6行中大约1500行代码)。
ObjectCreationHandling och = ObjectCreationHandling.Auto;
if( typeInitializer == null )
{
if( parameterlessConstructor )
{
och = ObjectCreationHandling.Reuse;
}
else
{
och = ObjectCreationHandling.Replace;
}
}
此设置是JsonSerializerSettings的一部分,它由DeserializeObject的访问者模式构造函数组成。如上所示,每个设置都有不同的功能。
回到List,Collection和ReadOnlyCollection,我们将查看每个条件语句的集合。
列表
testObj.List.GetType().TypeInitializer == null
是假的。因此,List
接收默认的ObjectCreationHandling.Auto,并且在反序列化期间使用testObj实例的实例化List,以及使用serialized
字符串实例化的新List。
testObj.List: ABC,DEF,Goodbye,AOL
集合
testObj.Collection.GetType().TypeInitializer == null
为true表示没有可用的反射类型初始值设定项,因此我们转到下一个检查是否存在无参数构造函数的条件。 testObj.Collection.GetType().GetConstructor(Type.EmptyTypes) == null
是假的。因此Collection
收到ObjectCreationHandling.Reuse(仅重用现有对象)的值。 Collection的实例化实例用于testObj,但serialized
字符串无法实例化。
testObj.Collection: ABC,DEF
ReadOnlyCollection
testObj.ReadOnlyCollection.GetType().TypeInitializer == null
为true表示没有可用的反射类型初始值设定项,因此我们转到下一个检查是否存在无参数构造函数的条件。 testObj.ReadOnlyCollection.GetType().GetConstructor(Type.EmptyTypes) == null
也是如此。因此,ReadOnlyCollection接收ObjectCreationHandling.Replace(总是创建新对象)的值。仅使用serialized
字符串中的实例化值。
testObj.ReadOnlyCollection: Goodbye,AOL
答案 1 :(得分:3)
虽然这已经解决了,但我想把这个答案原来发布给一个重复的问题,但是这个问题已经关闭了,所以我在这里发表我的答案,因为它包含了一些内部的看法。
因为Json.NET是开源的,所以我们可以幸运地找到它的根源:-)。
如果检查Json.NET源代码,可以找到处理反序列化(complete source here)的类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<T>
基础集合,IsReadOnlyOrFixedSize
返回false
和IsValueType()
返回false
- 因此会重用基础现有值。
对于Array
,IsValueType()
也是false
,但IsReadOnlyOrFixedSize
是true
的原因显而易见,因此useExistingValue
标志已设置false
方法中的CreateValueInternal
SetPropertyValue
调用null
调用会收到ObjectCreationHandling.Replace
引用,该引用不是重用现有值,而是创建新值,即然后设置新实例。
如上所述,可以使用useExistingValue
更改此行为,因为在CalculatePropertyDetails
方法中设置SoundPool
之前会对此进行检查。