web-dev-qa-db-ja.com

動的オブジェクトのシリアル化

私はDynamicObjectクラスをBinaryFormatterでシリアル化しようとしましたが、

  • 出力ファイルが大きすぎます。正確にワイヤフレンドリーではありません
  • 循環参照は処理されません(シリアライズ中にスタックします)

DynamicObjectをシリアル化すること自体はほとんど意味がないので、シリアル化しようとしたクラスは次のとおりです。

[Serializable()]
class Entity
    : DynamicObject, ISerializable
{

    IDictionary<string, object> values = new Dictionary<string, object>();

    public Entity()
    {

    }

    protected Entity(SerializationInfo info, StreamingContext ctx)
    {
        string fieldName = string.Empty;
        object fieldValue = null;

        foreach (var field in info)
        {
            fieldName = field.Name;
            fieldValue = field.Value;

            if (string.IsNullOrWhiteSpace(fieldName))
                continue;

            if (fieldValue == null)
                continue;

            this.values.Add(fieldName, fieldValue);
        }

    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        this.values.TryGetValue(binder.Name, out result);

        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        this.values[binder.Name] = value;

        return true;
    }        

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {            
        foreach (var kvp in this.values)
        {
            info.AddValue(kvp.Key, kvp.Value);                 
        }
    }

}

(ExpandoObjectを使用できたと思いますが、それはまた別の話です。)

簡単なテストプログラムを次に示します。

    static void Main(string[] args)
    {
        BinaryFormatter binFmt = new BinaryFormatter();

        dynamic obj = new Entity();
        dynamic subObj = new Entity();
        dynamic obj2 = null;

        obj.Value = 100;
        obj.Dictionary = new Dictionary<string, int>() { { "la la la", 1000 } };

        subObj.Value = 200;
        subObj.Name = "SubObject";

        obj.Child = subObj;

        using (var stream = new FileStream("test.txt", FileMode.OpenOrCreate))
        {
            binFmt.Serialize(stream, obj);                
        }

        using (var stream = new FileStream("test.txt", FileMode.Open))
        {
            try
            {
                obj2 = binFmt.Deserialize(stream);                    
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }                
        }

        Console.ReadLine();

    }

ブレークポイントをあちこちに置くと、obj2の内容を確認するのに役立ち、元のデータが正しく逆シリアル化されたように見えますが、想像力を駆使してデータを移動すると、上記の欠点が生じます。

私はMarc Gravellのprotobuf-netを見ましたが、そのようなコンテキストでそれをどのように使用するか本当にわかりません(リポジトリから適切なバージョンを選択したかどうかさえわかりませんが、ちょっと)。

言葉よりもコードの方が多いことはわかっていますが、シナリオをこれ以上説明できないと思います。この質問を明確にするために追加できることがあるかどうか教えてください。

どんな助けでも大歓迎です。

22
Raine

このシーケンスが動的オブジェクトで機能することを98%確信しています。

  1. オブジェクトをExpandoオブジェクトに変換する
  2. expandoオブジェクトをタイプDictionaryにキャストします
  3. 通常どおり、ProtoBuf-net Serializer.Serialize/.Deserializeを使用します
  4. 辞書をExpandoオブジェクトに変換

転送するために、オブジェクトを名前と値のペアのコレクションに変換できます。

これは、ダイナミックが実行できることのほんの一部ですが、おそらくそれで十分です。

上記の変換の一部を処理するためのカスタムコードがいくつかあります。興味がある場合は、それを紹介します。

動的がクラスのプレースホルダーである場合の解決策はありません。この場合、型を取得し、switchステートメントを使用して必要に応じてシリアル化/逆シリアル化することをお勧めします。この最後のケースでは、必要なジェネリック逆シリアル化のタイプを示す何かを配置する必要があります(文字列/ ID /完全修飾タイプ名/など)。想定されるのは、予想されるタイプのリストを扱っているということです。

注:ExpandoはIDictionaryを実装しています。 Expandoは、単なるキー/値ペアのリストです。すなわち。あなたが点を打つことがキーであり、値はそれを実装する関数のチェーンからの戻り値です。シンタックスシュガーエクスペリエンスをカスタマイズするための一連の動的インターフェイスがありますが、ほとんどの場合、それらを確認することはありません。

refs:

12
sgtz

JSONがあなたのシナリオで受け入れられるかどうかはわかりませんが、受け入れられる場合は、Json.net( http://json.codeplex.com )を使用して動的な型をシリアル化しました。それは非常にうまく機能し、高速で、出力のサイズは小さいです。 Json.netは動的オブジェクトを直接返しませんが、Json.Netの逆シリアル化された出力を任意の動的型に変換するのは非常に簡単です。以下の例では、ダイナミックタイプとしてExpandoObjectを使用しています。以下のコードは、Facebookグラフツールキットで使用したコードです。元のソースについては、このリンクを参照してください: http://facebookgraphtoolkit.codeplex.com/SourceControl/changeset/view/48442#904504

public static dynamic Convert(string s) {
            object obj = Newtonsoft.Json.JsonConvert.DeserializeObject(s);
            if (obj is string) {
                return obj as string;
            } else {
                return ConvertJson((JToken)obj);
            }
    }

    private static dynamic ConvertJson(JToken token) {
        // FROM : http://blog.petegoo.com/archive/2009/10/27/using-json.net-to-eval-json-into-a-dynamic-variable-in.aspx
        // Ideally in the future Json.Net will support dynamic and this can be eliminated.
        if (token is JValue) {
            return ((JValue)token).Value;
        } else if (token is JObject) {
            ExpandoObject expando = new ExpandoObject();
            (from childToken in ((JToken)token) where childToken is JProperty select childToken as JProperty).ToList().ForEach(property => {
                ((IDictionary<string, object>)expando).Add(property.Name, ConvertJson(property.Value));
            });
            return expando;
        } else if (token is JArray) {
            List<ExpandoObject> items = new List<ExpandoObject>();
            foreach (JToken arrayItem in ((JArray)token)) {
                items.Add(ConvertJson(arrayItem));
            }
            return items;
        }
        throw new ArgumentException(string.Format("Unknown token type '{0}'", token.GetType()), "token");
    }
10
Nathan Totten

まず、ファイルのサイズは2つの要素に依存します(BinaryFormatterのしくみを理解している場合は、誤りがあれば修正してください)。

  1. シリアル化される実際の値のサイズ、および
  2. SerializationInfo.AddValueメソッドを使用してオブジェクトの値をシリアル化するために使用される名前。出力ファイルに格納されるため、逆シリアル化中に同じ名前で値を使用できます。

明らかに、#1は最大のスローダウンを引き起こします。これは、シリアル化しようとしているオブジェクトを最適化することによってのみ軽減できます。

動的オブジェクトを使用しているため、#2によって引き起こされるほとんど気付かれないほど小さなサイズの増加は避けられません。オブジェクトのメンバーのタイプと名前が事前にわかっている場合は、繰り返しながら、オブジェクトの各メンバーに非常に短い、順次決定される名前(「1」、「2」、「3」など)を付けるだけで済みます。オブジェクトのメンバーの上に、SerializationInfo.AddValueを介して追加します。次に、逆シリアル化中に、同じ名前で順次決定されるSerializationInfo.GetValueを使用できます。逆シリアル化は、オブジェクトのメンバーを同じ順序で繰り返し処理する限り、逆シリアル化される値の実際の名前に関係なく、正常に機能します。確かに、これはメンバーあたり平均4バイトまたは5バイトを節約するだけかもしれませんが、これらの小さな量は大きなオブジェクトに追加される可能性があります。

@Raine:(私はExpandoObjectを使用できたと思いますが、それはまた別の話です。)

そうではありません。 ExpandoObjectクラスの代わりにEntityを使用するようにコードサンプルを変更したところ、SerializationExceptionがスローされました。 ExpandoObjectSerializableAttributeでマークされておらず、逆シリアル化またはシリアル化する適切なコンストラクターがありません。ただし、これは、本当に必要な場合にExpandoObjectを使用することできないことを意味するわけではありません。IDictionary<string, object>を実装し、次にICollection<KeyValuePair<string, object>>を実装します。したがって、ExpandoObjectインスタンスはKeyValuePair<string, object>インスタンスのコレクションであり、はシリアライズ可能としてマークされています。したがって、ExpandoObjectをシリアル化できますが、それをICollection<KeyValuePair<string, object>>としてキャストし、その中の各KeyValuePair<string, object>を個別にシリアル化する必要があります。ただし、元のコードサンプルの最適化という点では、ファイルスペースと同じだけの大きさであるため、これは無意味です。

要約すると、動的オブジェクトの直列化を最適化する方法はないと思います。直列化されるたびにオブジェクトのメンバーをループする必要があり、(動的の定義により)オブジェクトのサイズを事前に知る方法はありません。 。

1

SharpSerializerがダイナミックオブジェクトをサポートしているかどうかはわかりませんが、試してみる価値はあります。

http://www.sharpserializer.com/en/index.html

0
Nikolai Sander