2015-01-08 3 views
13

Я отслеживал ошибку, и я заметил, что Newtonsoft JSON добавит элементы к List<>, которые были инициализированы в конструкторе по умолчанию. Я немного поработал и обсудил с некоторыми людьми в чате C#, и мы заметили, что это поведение не распространяется на все другие типы коллекций.Пояснение для ObjectCreationHandling с использованием Newtonsoft JSON?

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

+0

Я подозреваю, что ответ будет «просто потому, что», также было бы интересно узнать, есть ли причина поведения для List/Collection (поведение ReadOnlyCollection несколько самоочевидно с моей точки зрения). Боковое замечание: подумайте о том, чтобы обновить название, чтобы быть более конкретным - «любопытно» несколько сложно догадаться, если бы захотелось найти это на основе проблемы ... –

+0

@AlexeiLevenkov - У меня возникают некоторые проблемы с выяснением нюансов между ReadOnlyCollection и Collection. Проблема, похоже, связана с параметром ObjectCreationHandling, поэтому я добавил это в заголовок. –

+0

Эта проблема также обсуждалась в [C# чате] (http://chat.stackoverflow.com/transcript/message/20857312#20857312). –

ответ

5

Это в основном сводится к созданию экземпляра и настройке ObjectCreationHandling. Существует три установки для ObjectCreationHandling

0 Повторно использовать существующие объекты, при необходимости создавать новые объекты.
Повторное использование 1 Повторное использование существующих объектов.
Заменить 2 Всегда создавать новые объекты.

По умолчанию auto (Line 44).

Авто перезаписывается только после серии проверок, которые определяют, имеет ли текущий тип TypeInitializer, который является нулевым. В этот момент он проверяет наличие конструктора без параметров.

///
/// Создать фабричную функцию, которая может быть использована для создания экземпляров JsonConverter описанного типа /// аргумента
.
/// Возвращенная функция затем может использоваться либо для вызова значения по умолчанию для ctor преобразователя, либо для любых конструкторов с параметризацией
/// посредством массива объектов.
///

По существу, он действует, как это (то, что он выглядит примерно 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 

Коллекция

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, который обрабатывает десериализацию (complete source here). Этот класс имеет метод 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.

Смежные вопросы