2015-01-08 19 views
13

私はバグを追跡していましたが、Newtonsoft JSONがデフォルトのコンストラクタで初期化されたList<>にアイテムを追加することに気付きました。私はもう少し掘り下げて、C#のチャットでいくつかの人々と議論し、この動作は他のすべてのコレクションタイプには当てはまらないことに気付きました。 Newtonsoft JSONを使用したObjectCreationHandlingについての説明?

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

出力:あなたはCollection<>プロパティは、逆シリアル化の影響を受けないで見ることができるように

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

List<>がに追加され、ReadOnlyCollection<>が交換されます。これは意図された動作ですか?推論は何でしたか?

+0

私は、List/Collection(ReadOnlyCollectionの振る舞いは私の視点からやや自明である)の振る舞いの裏側に理由があるかどうかを知ることは興味深いと思う。サイドノート:タイトルをより具体的に更新することを検討してください - 問題に基づいてこれを見つけたいと思うのであれば、 "好奇心"はやや難しいでしょう... –

+0

@AlexeiLevenkov - 私はReadOnlyCollectionとCollectionの間のニュアンスを考えています。問題はObjectCreationHandlingの設定にあるように見えるので、タイトルに追加しました。 –

+0

この問題は、[c#チャットルーム](http://chat.stackoverflow.com/transcript/message/20857312#20857312)でも議論されています。 –

答えて

5

基本的にはインスタンス化を入力してObjectCreationHandlingの設定になっています。 ObjectCreationHandling

Auto 0の3つの設定があります。必要に応じて既存のオブジェクトを再利用し、新しいオブジェクトを作成します。
再利用1既存のオブジェクトのみを再利用します。
2常に新しいオブジェクトを作成します。

デフォルトはautoLine 44)です。

自動は、現在のタイプがヌルであるTypeInitializerを持っているかどうかを判断する一連のチェックの後で上書きされます。その時点で、パラメータのないコンストラクタがあるかどうかをチェックします。

/// ///

///引数の型によって記述JsonConverterのインスタンスを作成するために使用することができるファクトリ関数を作成します。
///返された関数を使用して、コンバータのデフォルトのctor、またはオブジェクト配列を使用して
///パラメータ化されたコンストラクタを呼び出すことができます。
///

は、基本的にそれは(6クラス内のコードの約1500行であるようにそれが見えるもの)は、このような役割を果たします。

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

この設定はDeserializeObject用ビジターパターンコンストラクタの内部に構成されているJsonSerializerSettingsの一部です。上に示したように、各設定は異なる機能を持っています。

List、Collection、およびReadOnlyCollectionに戻って、それぞれの条件文のセットを調べます。

一覧

testObj.List.GetType().TypeInitializer == nullはfalseです。その結果、ListはデフォルトのObjectCreationHandling.Autoを受け取り、デシリアライズ時にはtestObjインスタンスのインスタンス化されたListが使用され、serialized文字列でインスタンス化された新しいListが使用されます。

testObj.List: ABC,DEF,Goodbye,AOL 

コレクション

testObj.Collection.GetType().TypeInitializer == nullは、利用可能な、反射タイプ初期化子がなかった示す事実であるので、我々は、パラメータなしのコンストラクタがあるかどうかを確認することで、次の条件に進みます。 testObj.Collection.GetType().GetConstructor(Type.EmptyTypes) == nullはfalseです。その結果、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ソースをチェックすると、デシリアライズ(complete source here)を処理するクラスJsonSerializerInternalReaderが見つかります。あなたが見ることができるように、既存の値が再利用されているか否かを決定するブールフラグuseExistingValueがある

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

:このクラスは、新しく作成されたオブジェクト(コード略す)にデシリアライズ値を設定する方法SetPropertyValueを有しますまたは置き換えられます。 - 従って、基礎となる既存の値が再利用されるList<T>基礎となるコレクションの場合

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

IsReadOnlyOrFixedSize戻りfalseIsValueType()戻るfalseCalculatePropertyDetails方法内側

は、次のコードです。 Arrayについて

は、IsValueType()falseであるが、IsReadOnlyOrFixedSizeしたがってuseExistingValueフラグがfalseに設定され、SetPropertyValue方法でCreateValueInternalコールインジケータは、既存を再利用することはないnull基準を受信し、明白な理由のためにtrueあります新しいインスタンスを作成し、新しいインスタンスに設定します。

前述したように、この動作は、を使用して変更することができます。これは、CalculatePropertyDetailsメソッドでuseExistingValueを設定する前にチェックされています。

関連する問題