2015-01-08 13 views
13

Ich habe einen Fehler ausfindig gemacht und festgestellt, dass Newtonsoft JSON Elemente an eine List<> anfügt, die im Standardkonstruktor initialisiert wurde. Ich habe ein wenig mehr gegraben und mit einigen Leuten über den C# -Chat gesprochen und festgestellt, dass dieses Verhalten nicht für alle anderen Sammlungstypen gilt.Erklärung für ObjectCreationHandling mit 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)); 
    } 
} 

Ausgang:

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

Wie Sie die Collection<> Eigenschaft ist unbeeinflusst von der Deserialisierung sehen kann, wird die List<> angehängt und ReadOnlyCollection<> ersetzt wird. Ist das beabsichtigtes Verhalten? Was war die Begründung?

+0

Ich vermute, die Antwort wäre "nur weil", auch wäre es interessant zu wissen, ob es einen Grund für Verhalten für List/Collection (ReadOnlyCollection Verhalten ist aus meiner Sicht etwas selbsterklärend). Randnotiz: Erwähnen Sie die Aktualisierung des Titels, um genauer zu sein - "neugierig" ist etwas schwer zu erraten, wenn man dies anhand des Problems finden möchte ... –

+0

@AlexeiLevenkov - Ich habe einige Probleme, herauszufinden, die Nuance zwischen ReadOnlyCollection und Collection. Das Problem scheint mit der ObjectCreationHandling-Einstellung zu sein und so habe ich das zum Titel hinzugefügt. –

+0

Dieses Problem wurde auch im [C# chat room] (http://chat.stackoverflow.com/transcript/message/20857312#20857312) diskutiert. –

Antwort

5

Im Grunde läuft es auf die Instanziierung und die ObjectCreationHandling Einstellung. Es gibt drei Einstellungen für ObjectCreationHandling

Auto 0 Bestehende Objekte wiederverwenden, neue Objekte bei Bedarf erstellen.
Wiederverwendung 1 Nur vorhandene Objekte erneut verwenden.
Ersetzen 2 Immer neue Objekte erstellen.

Der Standardwert ist auto (Line 44).

Auto wird nur nach einer Reihe von Überprüfungen überschrieben, die bestimmen, ob der aktuelle Typ eine TypeInitializer hat, die null ist. An diesem Punkt wird überprüft, ob ein parameterloser Konstruktor vorhanden ist.

///
/// eine Fabrik-Funktion erstellen, die verwendet werden können Instanzen eines JsonConverter vom Typ
/// Argument beschrieben zu erstellen.
/// Die zurückgegebene Funktion kann dann verwendet werden, um entweder den Standard-CTor des Konverters oder einen beliebigen parametrisierten Konstruktor
/// über ein Objekt-Array aufzurufen.
///

Im Wesentlichen handelt es sich wie folgt aus (was es sieht aus wie etwa 1500 Zeilen Code in 6 Klassen).

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

Diese Einstellung ist ein Teil der JsonSerializerSettings, die im Inneren des Besuchers Muster Konstruktor für DeserializeObject zusammengesetzt sind. Wie oben gezeigt, hat jede Einstellung eine andere Funktion.

Wenn wir zurück zu "List", "Collection" und "ReadOnlyCollection" gehen, sehen wir uns die einzelnen bedingten Anweisungen an.

Liste

testObj.List.GetType().TypeInitializer == null ist falsch. Als Ergebnis erhält List das standardmäßige ObjectCreationHandling.Auto, und die instanziierte Liste für die testObj-Instanz wird während der Deserialisierung verwendet, sowie eine neue Liste, die mit der serialized-Zeichenfolge instanziiert wird.

testObj.List: ABC,DEF,Goodbye,AOL 

Sammlung

testObj.Collection.GetType().TypeInitializer == null gilt anzeigt, dass es war kein reflektiertes Typeninitialisierer zur Verfügung, so gehen wir in die nächste Bedingung, die es zu prüfen ist, ob ein parameterlosen Konstruktor ist. testObj.Collection.GetType().GetConstructor(Type.EmptyTypes) == null ist falsch. Als Ergebnis erhält Collection den Wert von ObjectCreationHandling.Reuse (nur vorhandene Objekte wiederverwenden). Die instanziierte Instanz für Collection wird von testObj verwendet, aber die Zeichenfolge serialized kann nicht instanziiert werden.

testObj.Collection: ABC,DEF 

Readonlycollection

testObj.ReadOnlyCollection.GetType().TypeInitializer == null gilt anzeigt, dass es war kein reflektiertes Typeninitialisierer zur Verfügung, so gehen wir in die nächste Bedingung, die es zu prüfen ist, ob ein parameterlosen Konstruktor ist. testObj.ReadOnlyCollection.GetType().GetConstructor(Type.EmptyTypes) == null ist auch wahr. Als Ergebnis erhält ReadOnlyCollection den Wert von ObjectCreationHandling.Replace (immer neue Objekte erstellen). Nur der instanziierte Wert aus dem serialized-String wird verwendet.

testObj.ReadOnlyCollection: Goodbye,AOL 
2

Obwohl dies behoben wurde, wollte ich diese Antwort auf eine doppelte Frage stellen ursprünglich, aber diese Frage wurde geschlossen, so veröffentliche ich meine Antwort hier, wie es einigen internen Blick in dieser enthält.

Da Json.NET Open Source ist, können wir glücklicherweise den Grund bis zur Wurzel finden :-). Wenn Sie die Json.NET-Quelle überprüfen, können Sie die Klasse JsonSerializerInternalReader finden, die die Deserialisierung behandelt (complete source here). Diese Klasse hat eine Methode SetPropertyValue, die auf dem neu erstellten Objekt den entserialisierten Wert legt (Code abgekürzt):

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

Wie Sie sehen können, gibt es eine boolean-Flag useExistingValue, die bestimmt, ob der bestehende Wert wiederverwendet wird oder ersetzt werden.

Im Innern der CalculatePropertyDetails Methode ist der folgende Code-Schnipsel:

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

Bei der List<T> zugrunde liegenden Auflistung, die IsReadOnlyOrFixedSize kehrt false und IsValueType() kehrt false - daher der Basiswert vorhandene Wert wiederverwendet wird.

Für Array, IsValueType() ist auch false, aber die IsReadOnlyOrFixedSize ist true aus offensichtlichen Gründen, weshalb die useExistingValue Flag auf false und der CreateValueInternal Aufruf im SetPropertyValue Methode erhält eine null Referenz, die ein Indikator nicht das bestehende wiederzuverwenden Wert, sondern um einen neuen zu erstellen, der dann auf die neue Instanz gesetzt wird.

Wie bereits erwähnt, kann dieses Verhalten unter Verwendung von ObjectCreationHandling.Replace geändert werden, da dies überprüft wird, bevor useExistingValue in der CalculatePropertyDetails-Methode festgelegt wird.

Verwandte Themen