Подтвердить что ты не робот

Объяснение для 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<>. Это намеренное поведение? Каковы были рассуждения?

4b9b3361

Ответ 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.