-
C#
Posted on 1月 22nd, 2006 はおりん No comments■ [program] 最近C#をはじめた。
はじめて4週間足らずだが、壁紙チェンジャーを作り始めた。
C#のキモは(C#に限らないが)、try-catch-finallyだと思った。
.NET FrameworkのAPI郡はほとんどエラーを返さない代わりに、例外をばんばん出してくる。
適切に処理しないと、C言語の系列のクセに、昔のVBよろしく、実行時エラーで落ちるわけだ。
■ あと、よくわからないのが、アプリの設定保存。
昔はINIファイルなり、レジストリなりに保存していたのだが、.NET以降はユーザーのApplicationDataにXMLで保存するのがデファクトらしい。
XMLって、どう作ってどう読むんだ?
なんか簡単なラッパークラスはないのか?
■ と、いうわけで、今回はとりあえずシリアライズしてみた。
さらに、TreeViewとTreeNodeを継承して、FileTreeとFileTreeNodeというコンポーネントを作っているので、その中身を保存したときのシリアライズのカスタマイズなんかも書いておこうと思う。
■ 以前、シリアライズといえば、データをバイナリなりテキストなりの羅列にしてファイルに放り込むものだったのだが、.NETのシリアライズはもうちょっと進化しており、各データに名前を付けられる。
つまり、バージョンアップなどで保存すべきデータが増えてしまっても、名前を付けたデータの中身さえ変わっていなければ、シリアル化したデータは引き継げるということだ。
また、クラス内部で独自のデータをシリアルストリームに加える際、クラス内部で付けた名前は、他のインスタンスとは区別される。
つまり、
シリアルストリーム
├Class1
│├val1
│└val2
└Class2
├val1
└val2というふうに、Class1とClass2という2つのインスタンスがあって、その両方をシリアル化した際、以前は順番を守ってシリアル化し、順番を守ってデシリアル化しないといけなかったのが、今度のシリアライズでは、名前をキーとしてデータを呼び出すので、順番は関係なし。しかも、Class1 のval1、Class2のval1というふうに、内部でちゃんと管理されているので、クラス内のメンバをシリアル化する時に、グローバルな固有名は考えず、クラス内だけの固有名を決めておけばよい、と。
これまたずいぶんと、オレ的にいえばこんな便利な設定保存方法はないじゃないかと思うのだが、いかがなものだろうか。
■ ただし、問題がないわけじゃない。
それはデシリアライズの順番で、たとえば、TreeViewは内部にTreeNodeのコレクションを持っているが、TreeViewを継承して、シリアライズ可能なSerializableTreeViewのクラスを作ったとしよう。
そこで、SerializableTreeViewのGetObjectメソッド(オブジェクトをシリアライズするときに自動的に呼ばれる。ここで、シリアライズストリームにデータを追加する)の中で、TreeNodeを1つずつシリアライズしていったとする。
すると、逆シリアル化の際には、まずSerializableTreeViewの逆シリアル化コンストラクタが呼ばれ、その後にTreeNodeの逆シリアル化コンストラクタが呼ばれる。
すると、どういう問題が起こるのかというと、TreeViewの逆シリアル化コンストラクタが呼ばれた時点で、内部のTreeNodeをコレクションに追加していくのが楽な手法だと思ったのだが、追加すべきTreeNodeのインスタンスがまだ作られていないので、コレクションに追加できない。
TreeNodeの逆シリアル化コンストラクタでは、追加すべきTreeViewのインスタンスがわからないので、自分自身を追加することが出来ない。
ということで、オレの場合は、仕方がないのでTreeViewを継承したクラスにシリアル化を行わせるのではなく、シリアル化を行う前に、TreeNodeの配列を作って、それを直接シリアライズした。
というわけで、シリアライズを行う際には、まず、シリアライズしたいデータのハッシュテーブルを作って、それぞれのデータに一意な名前を付け、そのハッシュテーブルをシリアル化すると良いと思った。
もっといい方法があるのかもしれん。
■ さて、せっかくの備忘録なので、カスタムクラスをシリアル化する手順でも。
まず、シリアル化したいクラスにSerializable属性を付ける。
そして、ISerializableインターフェースを継承する。
継承したら、実装するってことで、ISerializable.GetObjectDataメソッドを実装する。
GetObjectDataはシリアライズメソッドなので、デシリアライズも必要なので、デシリアライズコンストラクタも実装する。
というわけで出来上がったサンプルがコレ。
using System; using System.IO; using System.Runtime.Serialization; using System.Collections; namespace SerialTest { [Serializable] class SerialTestClass : ISerializable { public int val1; public string val2; SerialTest() { } SerialTest(SerializationInfo si, StreamingContext context) { this.val1 = si.GetInt32("val1"); this.val2 = si.GetString("val2"); } void ISerializable.GetObjectData(SerializationInfo si, StreamingContext context) { si.AddValue("val1", this.val1); si.AddValue("val1", this.val2); } } class SerialTest { public void Main() { SerialTestClass stc = new SerialTestClass(); stc.val1 = 10; stc.val2 = "Test"; try { FileStream fs = new FileStream("test.dat", FileMode.Create); Formatters.Binary.BinaryFormatter formatter = new Formatters.Binary.BinaryFormatter(); Hashtable data = new Hashtable(); data["stc"] = stc; formatter.Serialize(fs, data); } catch(IOException ioe) { ファイル関連の例外処理 } catch(SerializationException see) { シリアル化関連の例外 } finally { fs.Close(); } } } }
■ というわけで、AddValueとGet~~だけで、簡単にデータの保存と取出しが出来るという備忘録。
過去日記コメントを書く