2009/10/12

BinaryFormatterにアセンブリバージョンを無視させる

シリアル化、逆シリアル化に使われる
BinaryFormatterクラスについて。

どうやら、
逆シリアル化するときに
BinaryFormatter.AssemblyFormatプロパティに
FormatterAssemblyStyle.Simpleを設定しても、
ちゃんと機能してくれない(アセンブリバージョンの違いなどを無視してくれない)
といバグがあるらしい。
特に、ジェネリックなクラスを逆シリアル化する場合に
この現象が起こるっぽい。

回避策としては、
BinnaryFormatter.Binderプロパティに、
SerializationBinder抽象クラスを実装した自作のクラスのインスタンスを設定する
というのが有効のようだ。

で、
SerializationBinderクラスの実装方法。

実装すべきは
Type BindToType( string assemblyName, string typeName )
というメソッドひとつのみ。
名前通りアセンブリ名と型名が引数として渡されるので、
アセンブリバージョンを無視したいなら
return Type.GetType( typeName );
とすればいいんじゃない?
と、思うところだが、そうはいかない場合がある。

ジェネリッククラス(または構造体)の場合、
typeNameには、例えば
List<BinaryFormatterTest.Hoge>
を逆シリアル化するときには、こんな感じの文字列がはいってくる。

System.Collections.Generic.List`1[[BinaryFormatterTest.Hoge, BinaryFormatterTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ef4f1793c250e8fa]]


つまり、型パラメータにアセンブリの厳密名が入ってしまっている。
これを
Type.GetType( typeName );
とすると、
VersionやCulture、PublicKeyTokenがすべて一致するアセンブリが
見つからない場合、例外がスローされる。
なので、アセンブリバージョンとかを無視させたいなら、
Type.GetType()にtypeNameを渡す前に
どうにかしてtypeNameの文字列中から型パラメータの
Versionやらなんやらの文字列を強引に削除しないといけない。

で、以下、そのサンプルコード。
結構、強引にやってますが、なんとかうまくいくみたい。
バグがあったらごめんなさい。



public class IgnoreVersionBinder
: SerializationBinder
{
public override Type BindToType
( string assemblyName, string typeName )
{
int i = typeName.IndexOf( '[' );
while( i >= 0 )
{
i = typeName.IndexOf( ',', i );
int end = typeName.IndexOf( ']', i );
typeName = typeName.Remove( i, end - i );

i = typeName.IndexOf( '[', i );
}

Type t = Type.GetType( typeName, false );
return t;
}
}