Объяснение для ObjectCreationHandling с использованием Newtonsoft JSON?
Я отслеживал ошибку, и я заметил, что Newtonsoft JSON добавит элементы в List<>
, которые были инициализированы в конструкторе по умолчанию. Я немного поработал и обсудил с некоторыми людьми в чате С#, и мы заметили, что это поведение не распространяется на все другие типы коллекций.
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
В основном это сводится к типу экземпляра и настройке ObjectCreationHandling
. Для ObjectCreationHandling
есть три параметра:
Авто 0 Повторное использование существующих объектов, при необходимости создайте новые объекты.
Повторное использование 1 Только повторное использование существующих объектов.
Заменить 2 Всегда создавать новые объекты.
По умолчанию используется auto
(строка 44).
Авто перезаписывается только после серии проверок, которые определяют, имеет ли текущий тип TypeInitializer
значение null. В этот момент он проверяет наличие конструктора без параметров.
///
/// Создаем функцию factory, которая может использоваться для создания экземпляров JsonConverter, описанных в файле /// тип аргумента.
/// Возвращенная функция затем может быть использована для вызова либо конвертора по умолчанию, либо любого другого /// параметризованные конструкторы с помощью массива объектов.
///
По сути, он действует как это (похоже, это примерно 1500 строк кода в 6 классах).
ObjectCreationHandling och = ObjectCreationHandling.Auto;
if( typeInitializer == null )
{
if( parameterlessConstructor )
{
och = ObjectCreationHandling.Reuse;
}
else
{
och = ObjectCreationHandling.Replace;
}
}
Этот параметр является частью настроек JsonSerializerSettings, которые состоят из конструктора шаблона посетителя для DeserializeObject. Как показано выше, каждый параметр имеет другую функцию.
Возвращаясь к списку, коллекции и ReadOnlyCollection, мы рассмотрим набор условных операторов для каждого из них.
Список
testObj.List.GetType().TypeInitializer == null
является ложным. В результате List
получает объект ObjectCreationHandling.Auto по умолчанию и экземпляр экземпляра для экземпляра testObj используется во время десериализации, а также новый экземпляр, созданный с помощью строки serialized
.
testObj.List: ABC,DEF,Goodbye,AOL
Collection
testObj.Collection.GetType().TypeInitializer == null
является истинным, указывая на то, что не было обнаруженного инициализатора отраженного типа, поэтому переходим к следующему условию, которое должно проверить, существует ли конструктор без параметров. testObj.Collection.GetType().GetConstructor(Type.EmptyTypes) == null
является ложным. В результате Collection
получает значение ObjectCreationHandling.Reuse(только для повторного использования существующих объектов). Экземпляр экземпляра для Collection используется из testObj, но строка serialized
не может быть создана.
testObj.Collection: ABC,DEF
ReadOnlyCollection
testObj.ReadOnlyCollection.GetType().TypeInitializer == null
является истинным, указывая на то, что не было найденного инициализатора отраженного типа, поэтому переходим к следующему условию, которое заключается в проверке наличия конструктора без параметров. testObj.ReadOnlyCollection.GetType().GetConstructor(Type.EmptyTypes) == null
также верно. В результате ReadOnlyCollection получает значение ObjectCreationHandling.Replace(всегда создает новые объекты). Используется только инстанцированное значение из строки serialized
.
testObj.ReadOnlyCollection: Goodbye,AOL
Ответ 2
Хотя это было решено, я хотел опубликовать этот ответ по первому вопросу, но этот вопрос был закрыт, поэтому я публикую здесь свой ответ, поскольку он содержит некоторые внутренние взгляды на это.
Поскольку 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<T>
базовой коллекции IsReadOnlyOrFixedSize
возвращает false
и IsValueType()
возвращает false
- следовательно, существующее существующее значение повторно используется.
Для Array
, IsValueType()
также false
, но IsReadOnlyOrFixedSize
является true
по понятным причинам, поэтому флаг useExistingValue
имеет значение false
и вызов CreateValueInternal
в SetPropertyValue
метод получает ссылку null
, которая является индикатором не для повторного использования существующего значения, а для создания нового, который затем устанавливается в новом экземпляре.
Как уже упоминалось, это поведение может быть изменено с помощью ObjectCreationHandling.Replace
, поскольку это проверено перед установкой useExistingValue
в методе CalculatePropertyDetails
.