1

次のようなクラスがあるとします。

class Person {
  public string Name;
  public Person Parent;
}

次に、2 つのオブジェクトを作成します。

...
Person mike = new Person("Mike");
Person jack = new Person("Jack");
jack.Parent = mike;
List<Person> family = new List<Person>();
people.Add(mike);
people.Add(jack);
...

文字列「Mike」は、オブジェクト mike への一意の参照を 1 回シリアル化 (維持) してから解決しますか? それとも 2 回シリアル化されますか?

4

1 に答える 1

3

ここでの答えは「場合による」です。protobuf 仕様にはオブジェクト識別子/再利用が含まれていないため、通常(デフォルトでは) これはツリーのシリアル化であり、データは複製されます。

これは、すべてのデフォルトの動作で protobuf-net を使用して調べることができます。

using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        Person mike = new Person { Name = "Mike" };
        Person jack = new Person { Name = "Jack" };
        jack.Parent = mike;
        List<Person> people = new List<Person>();
        people.Add(mike);
        people.Add(jack);

        var cloneOfEverything = Serializer.DeepClone(people);

        var newMike = cloneOfEverything.Single(x => x.Name == "Mike");
        var newJack = cloneOfEverything.Single(x => x.Name == "Jack");
        Console.WriteLine(jack.Parent.Name); // writes Miks as expected

        bool areSamePersonObject = ReferenceEquals(newMike, newJack.Parent);
        // False ^^^

        bool areSameStringInstance = ReferenceEquals(
            newMike.Name, newJack.Parent.Name);
        // True ^^^
    }
}
[ProtoContract]
class Person
{
    [ProtoMember(1)]
    public string Name;
    [ProtoMember(2)]
    public Person Parent;
}

所見:

  • ジャックの親は正しくマイクと呼ばれています
  • しかし、同じように見えるのは別のオブジェクトインスタンスです
  • これstringは同じインスタンスです - 実装の詳細として、protobuf-net には、データ内の同じ UTF-8 チャンクを検出し、同じstringインスタンスを再利用して割り当てを回避するコードが含まれています - しかし、データはバイナリに 2 回含まれていました
  • 情報のために、上記のツリーは24バイトかかります

ここで何が起こっているかを調査することでも、これを確認できます。

Person mike = new Person { Name = "Mike" };
mike.Parent = mike;
var clone = Serializer.DeepClone(mike);

ツリーとして記述しているため、次のエラーが発生します。

再帰の可能性が検出されました (オフセット: 1 レベル): 人

でも!ライブラリ固有の実装の詳細として、protobuf-net には、回すことができる多くのノブとダイヤルが含まれています。これらの 1 つは、オブジェクト ID に関連しています。Person参照 ID で動作するように切り替えることができます。

[ProtoContract(AsReferenceDefault=true)]
class Person {...}

これにより、バイナリのデータが変更され (追加のマーカーを含めるため)、その結果、同じ行が機能するようになりました。

Person mike = new Person { Name = "Mike" };
mike.Parent = mike;
var clone = Serializer.DeepClone(mike);
bool areSamePersonObject = ReferenceEquals(clone, clone.Parent);
// ^^^ true

これは実装固有の詳細を使用しており、他の実装を混乱させる可能性があることに注意してください。

AsReferenceDefaultPersonここでは、いつでも参照として扱われるべきであると述べています。より細かく制御するために、個別に使用できるオブジェクト[ProtoMember]も含まれています。AsReferenceただし、簡単なチェックでは、現在正常に動作していないList<Person>ことが示されているようです。調査する必要があります。もっともな理由があるかもしれませんが、現時点では考えられず、バグであると思われます。

AsReferencestring同じ文字列を繰り返し書き込むことを避けるために、メンバーに含めることもできます"Mike"stringこのオプションは、同じことが何度も繰り返される場合に役立ちます。名前にもかかわらず、 を使用する場合stringAsReferenceは「参照の等価性」ではなく「文字列の等価性」として解釈されます。

于 2013-09-12T15:01:16.087 に答える