2015-01-08 7 views
13

Stavo rintracciando un bug e ho notato che Newton JSON aggiungerà gli articoli a un List<> che è stato inizializzato nel costruttore predefinito. Ho fatto un po 'più di scavo e discusso con alcune persone nella chat in C# e abbiamo notato che questo comportamento non si applica a tutti gli altri tipi di raccolta.Spiegazione per ObjectCreationHandling utilizzando Newton 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)); 
    } 
} 

uscita:

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

Come si può vedere la proprietà Collection<> non è influenzato dalla deserializzazione, il List<> viene aggiunto e ReadOnlyCollection<> è sostituito. È questo comportamento previsto? Qual è stato il ragionamento?

+0

Sospetto che la risposta sarebbe "solo perché", inoltre sarebbe interessante sapere se c'è un motivo dietro il comportamento per List/Collection (il comportamento di ReadOnlyCollection è in qualche modo autoesplicativo dal mio punto di vista). Nota a margine: considera l'aggiornamento del titolo per essere più specifico - "curioso" è un po 'difficile da indovinare se uno vorrebbe trovare questo in base al problema ... –

+0

@AlexeiLevenkov - Sto avendo alcuni problemi a capire le sfumature tra ReadOnlyCollection e Collection. Il problema sembra essere con l'impostazione ObjectCreationHandling e così l'ho aggiunto al titolo. –

+0

Questo problema è stato discusso anche nella [chat room C#] (http://chat.stackoverflow.com/transcript/message/20857312#20857312). –

risposta

5

Si riduce in sostanza al tipo di istanziazione e all'impostazione ObjectCreationHandling. Esistono tre impostazioni per ObjectCreationHandling

Auto 0 Riutilizzare oggetti esistenti, creare nuovi oggetti quando necessario.
Riutilizzo 1 Riutilizzare solo gli oggetti esistenti.
Sostituisci 2 Crea sempre nuovi oggetti.

Il valore predefinito è auto (Line 44).

Auto viene sovrascritto solo dopo una serie di controlli che determinano se il tipo corrente ha un valore TypeInitializer che è nullo. A quel punto controlla se esiste un costruttore senza parametri.

///
/// Creare una funzione di fabbrica che può essere utilizzata per creare istanze di un JsonConverter descritti dal tipo /// argomento
.
/// La funzione restituita può quindi essere utilizzata per invocare il ctor predefinito del convertitore o qualsiasi /// per i costruttori parametrizzati mediante un array di oggetti.
///

Essenzialmente agisce simili (come appare è circa 1500 linee di codice in 6 classi).

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

Questa impostazione è una parte dei JsonSerializerSettings che sono composte all'interno del costruttore modello visitor per DeserializeObject. Come mostrato sopra, ogni impostazione ha una funzione diversa.

Tornando a Elenco, Raccolta e ReadOnlyCollection, esamineremo l'insieme di istruzioni condizionali per ciascuno.

Lista

testObj.List.GetType().TypeInitializer == null è falso. Di conseguenza, List riceve l'ObjectCreationHandling predefinito. Auto e l'Elenco istanziato per l'istanza testObj viene utilizzato durante la deserializzazione, nonché una nuova lista creata con la stringa serialized.

testObj.List: ABC,DEF,Goodbye,AOL 

Collection

testObj.Collection.GetType().TypeInitializer == null vale indicando non c'era inizializzatore di tipo riflesso disponibile, così andiamo a condizione successiva che è quello di verificare se v'è un costruttore senza parametri. testObj.Collection.GetType().GetConstructor(Type.EmptyTypes) == null è falso. Di conseguenza, Collection riceve il valore di ObjectCreationHandling.Reuse (riutilizza solo gli oggetti esistenti). L'istanza istanziata per Collection viene utilizzata da testObj, ma la stringa serialized non può essere istanziata.

testObj.Collection: ABC,DEF 

ReadOnlyCollection

testObj.ReadOnlyCollection.GetType().TypeInitializer == null vale indicando non c'era inizializzatore di tipo riflesso disponibile, così andiamo a condizione successiva che è quello di verificare se v'è un costruttore senza parametri. testObj.ReadOnlyCollection.GetType().GetConstructor(Type.EmptyTypes) == null è anche vero. Di conseguenza, ReadOnlyCollection riceve il valore di ObjectCreationHandling.Replace (crea sempre nuovi oggetti). Viene utilizzato solo il valore istanziato dalla stringa serialized.

testObj.ReadOnlyCollection: Goodbye,AOL 
2

Anche se questo è stato risolto, ho voluto pubblicare questa risposta ad una domanda duplicato in origine, ma che domanda è stata chiusa, quindi sto pubblicando la mia risposta qui, in quanto contiene alcune sguardo interno in questo.

Poiché Json.NET è open source, possiamo rintracciare per fortuna il motivo fino alla sua radice :-).

Se si controlla il sorgente Json.NET, è possibile trovare la classe JsonSerializerInternalReader che gestisce la deserializzazione (complete source here). Questa classe ha un metodo SetPropertyValue, che imposta il valore deserializzato l'oggetto appena creato (codice abbreviato):

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; 
} 

Come potete vedere, c'è un flag booleano useExistingValue che determina se il valore esistente viene riutilizzato o sostituito.

Nel metodo CalculatePropertyDetails è il seguente frammento:

 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()); 
      } 
     } 

Nel caso della raccolta sottostante List<T>, i rendimenti IsReadOnlyOrFixedSizefalse e IsValueType() ritorni false - quindi il valore esistente sottostante viene riutilizzato.

Per Array, IsValueType() è anche false, ma il IsReadOnlyOrFixedSize è true per ovvie ragioni, quindi il flag useExistingValue è impostato false e la chiamata CreateValueInternal nel metodo SetPropertyValue riceve un riferimento null che è un indicatore non riutilizzare esistente valore, ma per crearne uno nuovo, che viene quindi impostato sulla nuova istanza.

Come si è detto, questo comportamento può essere modificato utilizzando ObjectCreationHandling.Replace, poiché questo viene controllato prima di impostare il useExistingValue nel metodo CalculatePropertyDetails.