上位互換

ファイルを読み書きできるソフトを更新して、ファイルフォーマットが変わったときにも容易に上位互換を保つ方法。
ファイル形式をXMLにしておけば、要素が増えても減っても対応できるが、構造が変わるとダメだ。
以下のようにすれば、古い型 DataDoc1のファイルでも新しい型 DataDocのファイルでもそのまま読める。

[Version(1)]
[XmlRoot("DataDoc")]
public class DataDoc1
{
	public int Num;
	public DataDoc Create()
	{
		DataDoc dd = new DataDoc();
		dd.NumList.Add(Num);
		return dd;
	}
}
[Version(2)]
public class DataDoc
{
	public int Version
	{
		get
		{
			VersionAttribute[] va = (VersionAttribute[])GetType().
				GetCustomAttributes(typeof(VersionAttribute), false);
			return va[0].Version;
		}
		set { }
	}
	[XmlArrayItem("Num")]
	public List<int> NumList;
	public DataDoc() { NumList = new List<int>(); }
	public static DataDoc Load(string fnam)
	{
		DataDoc dd = null;
		string s;
		int version = -1;
		using (StreamReader sr = new StreamReader(fnam))
		{
			while ((s = sr.ReadLine().Trim()) != null)
				if (s.StartsWith("<Version>")) break;
			if (string.IsNullOrEmpty(s)) return dd;
			version = int.Parse(s.Substring(9, s.IndexOf('<', 1) - 9));
		}
		using (StreamReader sr = new StreamReader(fnam))
		{
			XmlSerializer xs = new XmlSerializer(VesionedType(version));
			object o = xs.Deserialize(sr);
			MethodInfo mi = o.GetType().GetMethod("Create");
			dd = (DataDoc)mi.Invoke(o, new object[] { });
		}
		return dd;
	}
	public static Type VesionedType(int version)
	{
		Assembly asm = Assembly.GetExecutingAssembly();
		foreach (Type typ in asm.GetTypes())
		{
			VersionAttribute[] va = (VersionAttribute[])typ.
				GetCustomAttributes(typeof(VersionAttribute), false);
			if (va.Length > 0 && va[0].Version == version) return typ;
		}
		return null;
	}
}
/// <summary>バージョン付与</summary>
[AttributeUsage(AttributeTargets.Class)]
public class VersionAttribute : Attribute
{
	/// <summary>バージョン</summary>
	public int Version;
	/// <summary>コンストラクタ</summary>
	public VersionAttribute(int v) { Version = v; }
}
  • バージョンはint型で管理しているが、stringでもよい。
  • クラスごとにVersionAttributeでバージョンを付ける。
  • 古い型から新しい型に変換するCreateメソッドを用意する。