web-dev-qa-db-ja.com

クラスのソースコードがない場合、オブジェクトの.NETバイナリシリアル化を行うことは可能ですか?

BinaryFormatterを使用して、C#でいくつかのオブジェクトのバイナリシリアル化を行っています。ただし、一部のオブジェクトには、DLLを介してアクセスするクラスが含まれており、ソースコードがないため、Serializable属性でマークすることはできません。とにかくそれらをシリアル化する簡単な方法はありますか?クラスNoSourceを取得し、コンストラクターがSerializableNoSourceオブジェクトを取得してすべてを抽出する新しいクラスNoSourceを作成することを含む回避策があります。そこから必要な情報ですが、ハッキーです。もっと良い選択肢はありますか?

24
Craig W

シリアル化サロゲートを作成できます。

参照されているアセンブリで定義されているクラスがあり、それを制御できないと想像してみてください。次のようになります。

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public DriversLicense License;
}


// An instance of this type will be part of the object graph and will need to be 
// serialized also.
public class DriversLicense
{
    public string Number { get; set; }
}

このオブジェクトをシリアル化するには、オブジェクトグラフのタイプごとにシリアル化サロゲートを定義する必要があります。

シリアル化サロゲートを作成するには、 ISerializationSurrogate インターフェイスを実装する型を作成する必要があります。

public class PersonSurrogate : ISerializationSurrogate
{
    /// <summary>
    /// Manually add objects to the <see cref="SerializationInfo"/> store.
    /// </summary>
    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        Person person = (Person) obj;
        info.AddValue("Name", person.Name);
        info.AddValue("Age", person.Age);
        info.AddValue("License", person.License);
    }

    /// <summary>
    /// Retrieves objects from the <see cref="SerializationInfo"/> store.
    /// </summary>
    /// <returns></returns>
    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        Person person = (Person)obj;
        person.Name = info.GetString("Name");
        person.Age = info.GetInt32("Age");
        person.License = (DriversLicense) info.GetValue("License", typeof(DriversLicense));
        return person;
    }
}

public class DriversLicenseSurrogate : ISerializationSurrogate
{
    /// <summary>
    /// Manually add objects to the <see cref="SerializationInfo"/> store.
    /// </summary>
    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        DriversLicense license = (DriversLicense)obj;
        info.AddValue("Number", license.Number);
    }

    /// <summary>
    /// Retrieves objects from the <see cref="SerializationInfo"/> store.
    /// </summary>
    /// <returns></returns>
    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        DriversLicense license = (DriversLicense)obj;
        license.Number = info.GetString("Number");
        return license;
    }
}

次に、IFormatterを定義して初期化し、それをSurrogateSelectorに割り当てることにより、サロゲートについてIFormatterに通知する必要があります。

private static void SerializePerson(Person person)
{
    if (person == null)
        throw new ArgumentNullException("person");

    using (var memoryStream = new MemoryStream())
    {
        //Configure our surrogate selectors.
        var surrogateSelector = new SurrogateSelector();
        surrogateSelector.AddSurrogate(typeof (Person), new StreamingContext(StreamingContextStates.All),
                                       new PersonSurrogate());
        surrogateSelector.AddSurrogate(typeof (DriversLicense), new StreamingContext(StreamingContextStates.All),
                                       new DriversLicenseSurrogate());

        //Serialize the object
        IFormatter formatter = new BinaryFormatter();
        formatter.SurrogateSelector = surrogateSelector;
        formatter.Serialize(memoryStream, person);

        //Return to the beginning of the stream
        memoryStream.Seek(0, SeekOrigin.Begin);

        //Deserialize the object
        Person deserializedPerson = (Person) formatter.Deserialize(memoryStream);
    }
}

シリアル化サロゲートの使用は決して簡単ではなく、シリアル化しようとしているタイプにシリアル化が必要なプライベートフィールドと保護フィールドがある場合、実際には非常に冗長になる可能性があります。

しかし、すでに必要な値を手動でシリアル化しているので、それは問題ではないと思います。サロゲートの使用は、このようなシナリオを処理するためのより統一された方法であり、より快適に感じるはずです。

24
User 12345678

Mono.Cecil を使用して[SerializableAttribute]をクラスに追加できる場合がありますが、目的の結果を達成する別の方法がある場合は追加しません。

1
erikkallen

これは古い質問ですが、最近、パフォーマンス上の理由から、バイナリ形式のソースコードを制御できないDTOをシリアル化/逆シリアル化する必要があることがわかりました。 ZeroFormatterやProtobufなど、かなりの数の代替バイナリシリアライザーを見つけることができましたが、それらはすべて、DTOを属性で装飾するか、スキーマを定義する必要があります。

それはあなたが役に立つと思うかもしれないバイナリ形式のJSONシリアライザーの迅速な代替品である私自身のバイナリシリアライザーを作成する道に私を導きました: https://github.com/zachsaw/Binaron.Serializer

0
Zach Saw

@Servyに同意します。クラスの作成者がシリアル化されることを予期していなかった場合は、直接シリアル化を試みるべきではありません。つまり、アーキテクチャの観点から正しいことをしているのです。現在のアプローチを「ハッキー」にするために、シリアル化できないオブジェクトへの参照を含むクラスに ISerializable を実装することを検討してください。

0
Paul Keister

新しいクラスを作成し、シリアル化属性でマークされていない既存のクラスを継承し、ISerializableインターフェイスを実装します。

クラスが封印されている場合は、Json.NETを使用してから、バイナリに変換したり、その逆を行ったりすることができます(非常に時間がかかるので、他に何も役に立たない場合は使用してください:))。

0