10

C#ゲームでBinaryFormatterを使用して、ユーザーのゲームの進行状況やゲームレベルなどを保存します。下位互換性の問題が発生しています。

目的:

  • レベルデザイナーがキャンペーン(レベルとルール)を作成し、コードを変更しても、キャンペーンは正常に機能するはずです。これは、リリース前の開発中に毎日発生する可能性があります。
  • ユーザーがゲームを保存します。ゲームパッチをリリースします。ユーザーは引き続きゲームをロードできるはずです。
  • 目に見えないデータ変換プロセスは、2つのバージョンがどれほど離れていても機能するはずです。たとえば、ユーザーは最初の5つのマイナーアップデートをスキップして、6番目のアップデートを直接取得できます。それでも、彼の保存したゲームはまだ正常にロードされるはずです。

ソリューションは、ユーザーやレベルデザイナーには完全に見えないようにする必要があり、何かを変更したいコーダーに最小限の負担をかける必要があります(たとえば、より良い名前を考えたためにフィールドの名前を変更します)。

シリアル化するオブジェクトグラフの中には、あるクラスに根ざしているものもあれば、他のクラスに根ざしているものもあります。上位互換性は必要ありません。

変更を壊す可能性があります(古いバージョンをシリアル化し、新しいバージョンに逆シリアル化するとどうなりますか):

  • フィールドを追加します(デフォルトで初期化されます)
  • フィールドタイプの変更(失敗)
  • フィールドの名前を変更します(フィールドを削除して新しいフィールドを追加するのと同じです)
  • プロパティをフィールドに変更して元に戻します(名前の変更に相当)
  • 自動実装プロパティを変更して、バッキングフィールドを使用するようにします(名前の変更に相当)
  • スーパークラスを追加します(現在のクラスにフィールドを追加するのと同じです)
  • フィールドを別の方法で解釈します(たとえば、度単位、現在はラジアン単位)
  • ISerializableを実装する型の場合、ISerializableメソッドの実装を変更する場合があります(たとえば、非常に大きな型のISerializable実装内で圧縮の使用を開始します)
  • クラスの名前を変更し、列挙値の名前を変更します

私はについて読んだ:

私の現在の解決策

  • OnDeserializingコールバックなどを使用して、可能な限り多くの変更を中断せずに行います。
  • 重大な変更は2週間に1回の予定であるため、維持する互換性コードは少なくなります。
  • 重大な変更を加える前に、使用するすべての[Serializable]クラスを、OldClassVersions.VersionX(Xは最後の序数の次の序数)という名前空間/フォルダーにコピーします。すぐにリリースする予定がない場合でも、これを行います。
  • ファイルに書き込む場合、シリアル化するのはこのクラスのインスタンスです。class SaveFileData {int version; オブジェクトデータ; }
  • ファイルから読み取るとき、SaveFileDataを逆シリアル化し、次のようなことを行う反復的な「更新」ルーチンに渡します。

for(int i = loadedData.version; i < CurrentVersion; i++)
{
    // Update() takes an instance of OldVersions.VersionX.TheClass
    // and returns an instance of OldVersions.VersionXPlus1.TheClass
    loadedData.data = Update(loadedData.data, i);
}
  • 便宜上、Update()関数は、その実装で、リフレクションを使用して古いバージョンから新しいバージョンにできるだけ多くのデータをコピーするCopyOverlappingPart()関数を使用できます。このように、Update()関数は、実際に変更されたもののみを処理できます。

それに関するいくつかの問題:

  • デシリアライザーは、クラスOldClassVersions.Version5.FooではなくクラスFooにデシリアライズします。これは、クラスFooがシリアル化されたものであるためです。
  • テストやデバッグはほとんど不可能です
  • 多くのクラスの古いコピーを保持する必要がありますが、これはエラーが発生しやすく、壊れやすく、煩わしいものです。
  • クラスの名前を変更したいときにどうしたらよいかわかりません

これは本当に一般的な問題です。人々は通常どのようにそれを解決しますか?

4

3 に答える 3

3

難しいもの。バイナリをダンプし、XMLシリアル化を使用します(管理が簡単で、極端ではない変更に耐性があります-フィールドの追加/削除など)。より極端なケースでは、あるバージョンから別のバージョンへの変換(おそらくxslt)を記述して、クラスをクリーンに保つ方が簡単です。不透明度とディスクフットプリントが小さいことが要件である場合は、ディスクに書き込む前にデータの圧縮を試みることができます。

于 2010-08-27T11:47:36.670 に答える
2

ユーザープロファイルデータ(グリッド列の配置、フィルター設定など)の保存に関して、アプリケーションで同じ問題が発生しました。

私たちの場合、問題はAssemblyVersionでした。

この問題についてSerializationBinderは、アセンブリの実際のアセンブリバージョンを読み取るを作成します(すべてのアセンブリは、新しい展開で新しいバージョン番号を取得します) Assembly.GetExecutingAssembly().GetName().Version

オーバーライドされたメソッドBindToTypeでは、タイプ情報は新しいアセンブリバージョンで作成されます。

デシリアライズは「手動で」実装されます。つまり、

  • 通常のBinaryFormatterを介して逆シリアル化
  • デシリアライズする必要があるすべてのフィールドを取得します(独自の属性で注釈を付けます)
  • デシリアライズされたオブジェクトからのデータでオブジェクトを埋める

3つまたは4つのリリース以降、すべてのデータで機能します。

于 2010-08-27T11:47:31.723 に答える