BinaryFormatter#Deserialize(3)

きっかけはやはりMSDN。BinaryFormatterクラスのBinderプロパティから。

public virtual SerializationBinder Binder {get; set;}

対象のフォーマッタで使用するシリアル化バインダ。

更に、このプロパティの型であるSerializationBinderクラスの解説にはこうある。

MSDNより引用:
シリアル化中に、フォーマッタは、正しい型およびバージョンのオブジェクトのインスタンスを作成するために必要な情報を送信します。〜中略〜 クラスがアセンブリ間を移動したためか、異なるバージョンのクラスがサーバー上およびクライアント上で必要であるためのいずれかの理由で、一部のユーザーはどのクラスを読み込むかを制御する必要があります。
継承時の注意: SerializationBinder から継承する場合は、 BindToType のメンバをオーバーライドする必要があります。

この解説で特に気になるのは「クラスがアセンブリ間を移動したためか、異なるバージョンのクラスがサーバー上およびクライアント上で必要であるためのいずれかの理由で、一部のユーザーはどのクラスを読み込むかを制御する必要があります。」という部分。なるほど、フォーマッタが扱うクラスのバージョンが変わってしまった場合でも、バインダクラスによって違いを吸収できるらしい。
今回のケースでは、IPCを行う二つのアプリケーションが、同一のアセンブリに属する同一の型を使って情報をやり取りするわけで、アセンブリやクラスのバージョンは変わらないはずだ。それでもデシリアライズが上手くいかないのは、.NETの場合、同一のアセンブリ、同一の型であっても、違う場所に配置されているアセンブリや型は"同一"とみなされないからだろう。(ここは推測の域を出ない。どなたか、この部分に関しての確信が書いてある文献やサイト、書籍があれば、是非教えて頂だけないだろうか)

ということで解決するために必要なのは、フォーマッタを助ける適切なSerializationBinderクラスを実装して、BindToTypeメソッドをオーバライドしてやることだろう、ということでBindToTypeメソッドだけをオーバライドしたバインダクラスを準備した。

public class CopyDataObjectDataDeserializationBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
         Type typeToDeserialize = null;
         typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
         return typeToDeserialize;
    }
}

今回はバージョンの違いや型自体の違いを吸収するのが目的ではなく、違う場所に配置されたアセンブリの同一の型を使いたいのだから、メソッドの実装は極めて単純になる。用意した実装もパラメタとして渡されたアセンブリ名と型名からTypeオブジェクトを生成して返すだけだ。
バインダクラスが用意できたならば、あとは簡単。フォーマッタクラスのBinderプロパティにバインダクラスのインスタンスをセットしてやるだけだ。

BinaryFormatter formatter = new BinaryFormatter();
formatter.Binder = new CopyDataObjectDataDeserializationBinder();
CopyDataObjectData cdo  = formatter.Deserialize(stream) as CopyDataObjectData;

結果はビンゴ。これで全て上手くいった。実際のDesrializeメソッドの実行時には、必要な時にBindToTypeメソッドが呼ばれているのを確認できる。

.NETのこのあたりの話は非常が奥が深く、いろいろと試したりしていると疑問と興味がどんどん湧いてくる。それを調べ進めていくのはとても面白いんだが、どうにもこうにもJavaに比べて情報が少な過ぎるような気がする。