DOBON.NET DOBON.NETプログラミング掲示板過去ログ

アプリの設定クラスをXMLシリアライズ、シングルトン化したい

環境/言語:[Windows 7(x64) .NET Framework 4.0]
分類:[.NET]

2011/12/11(Sun) 10:26:46 編集(投稿者)

Visual Studio 2010 を使っています。

アプリケーションの設定を XML シリアライズ・デシリアライズで保存する
記事(http://dobon.net/cgi-bin/pc/pc.php#post_comment)
を拝見しました。とても参考になります。

今回私は、アプリケーションの設定クラスをシングルトン化したい
と思っているのですが、XML シリアライズ・デシリアライズにて
プロパティをファイルへ読み書きしているクラスを
シングルトン(アセンブリ中で唯一のインスタンスを持つクラス)
にする方法はありませんでしょうか?

自分で調べたのですが見つからず・・・
ひとまず、xml 化しない形で作ってみました。
アプリケーションの設定クラスで、
Shared で各プロパティを定義して、
Shared コンストラクタでファイルから
プロパティを読み込む形で作りました。

アプリケーション設定クラスは
プロパティが多いため、保存・読み込みは
シリアライズ・デシリアライズの仕組みを導入したいのです。

どのようにしたらよいのでしょうか・・

| Public Class Settings
|  Shared Property Name As String
|  Shared Property Age As Integer
|  Shared Property ……(以下、プロパティがたくさんあります。)
|
|  Shared Sub New()
|   ' プロパティをファイルから読み込む処理
|   (プロパティがたくさんあるので、xml デシリアライズをしたいです)
|  End Sub
|
| ……(略)
|
| End Class
今書いてある内容はシングルトンというより、静的なプロパティ群ですね。
「シングルトン VB.NET」で検索すると、VB.NET でのシングルトンの実装事例が出てくると思いますが、参考になりませんか?

・シングルトンインスタンスへのアクセスは Shared プロパティからのみ認める。
・Shared プロパティはインスタンスが生成されていなければ、XmlSerializer でデシリアライズしたインスタンスを保持する。
・Shared プロパティは保持しているインスタンス、またはデシリアライズされたインスタンスを返す。
・返されたインスタンスに対して、読み書きする。
・アプリ終了前、あるいは設定を保存すべきタイミングで、Shared プロパティで取得できたインスタンスに対して、Save メソッド(自分で実装する)を使い、シリアライズする。

なお、Shared プロパティの初回アクセスがマルチスレッドで同時に起きることが予想される場合、インスタンス生成が多重に行われる可能性があります。
必要に応じて排他処理を考えてください。
2011/12/12(Mon) 02:19:35 編集(投稿者)

■No29474に返信(Azuleanさんの記事)

Azurean 様

ご教授ありがとうございます。
いろいろ調べて頭の中が混乱していたのですが、
まさに大切なポイントを箇条書きにして下さったお陰様で、
整理できました。

以下のコードを考えているのですが、気になるところがあればご指摘下さい。
コードは一番下にあります。

コードの簡単な説明です。

@ _Instance フィールドは、宣言文の場所で初期化( New )しています。
 これは、マルチスレッドからアクセスされる場合でもインスタンス生成が多重に行われないようにするための対策になっていますでしょうか?

A この Instance プロパティからのみ、シングルトンインスタンスへのアクセスを認める形にしたつもりです。

B Shared コンストラクタです。この Shared コンストラクタ内で、
  XML ファイルからデシリアライズしようと考えています。
 このようにしておけば、宣言文での初期時(@)に、デシリアライズされるでしょうか?

 シリアライズには引数なしのコンストラクタが必要だと、どこかで読んだのですが、
 コンストラクタの中で、このようなデシリアライズ処理を行っても問題ないのでしょうか?
 それとも、別途、Private の Sub New() を作っておかなければならないのでしょうか?

C 読み込みメソッド Load() です。読み込み成功したら True を返します。
  クラス外から呼び出されることがないので、Private にしています。
  Save メソッドは、これから考えようと思っています。アプリケーションの終了時に
  呼び出したいので、Public で作るつもりです。

D Using 句は使いなれないのですが、エラー処理は、これで大丈夫でしょうか? Using 句を使っているだけです。

コード
| Public Class Settings
|
|  Property Name As String
|  Property Age As Integer
|  Property ……(以下、プロパティがたくさんあります。)
|
|  ' Settingsクラスのただ一つのインスタンス( New で初期化)
|  <NonSerialized()> _
|  Private Shared _instance As New Settings  …@
|
|  <System.Xml.Serialization.XmlIgnore()> _
|  Shared Property Instance() As Settings
|   Get
|    Return _instance            …A
|   End Get
|   Set(ByVal Value As Settings)
|    _instance = Value
|   End Set
|  End Property
|
|
|  Shared Sub New()              …B
|   ' コンストラクタ
|   ' プロパティをファイルから読み込む処理
|   If Not Load() Then
|    ' ファイル読み込みに失敗した場合
|    ' 規定値による初期化を行う。
|    Init() …Name, Age 等の各プロパティへ初期値をセットするメソッド。内容は割愛。
|   End If
|
|  End Sub
|
|  Private Shared Function Load() As Boolean  …C
|   ' 設定情報ファイルから設定情報を読み込む
|   Dim rc As Boolean = True
|
|   Dim filename As String _
|    = IO.Path.Combine(Application.StartupPath, "settings.xml")
|
|   If Not IO.File.Exists(filename) Then
|    ' 設定情報ファイルが存在しない場合
|    MessageBox.Show("設定情報ファイルがありません")
|    rc = False
|    Return rc
|   Else
|    ' xml デシリアルを行う
|    Dim XmlSerializer As New Xml.Serialization.XmlSerializer(GetType(Settings))
|
|    Using fs As New IO.FileStream(filename, IO.FileMode.Open) …D
|     ' デシリアル
|     Instance = XmlSerializer.Deserialize(fs)
|    End Using
|   End If
|
|   Return rc
|  End Function
|
| End Class


Name プロパティへのアクセスは、Settings.instance.Name としてアクセス。


> 今書いてある内容はシングルトンというより、静的なプロパティ群ですね。
> 「シングルトン VB.NET」で検索すると、VB.NET でのシングルトンの実装事例が出てくると思いますが、参考になりませんか?
>
> ・シングルトンインスタンスへのアクセスは Shared プロパティからのみ認める。
> ・Shared プロパティはインスタンスが生成されていなければ、XmlSerializer でデシリアライズしたインスタンスを保持する。
> ・Shared プロパティは保持しているインスタンス、またはデシリアライズされたインスタンスを返す。
> ・返されたインスタンスに対して、読み書きする。
> ・アプリ終了前、あるいは設定を保存すべきタイミングで、Shared プロパティで取得できたインスタンスに対して、Save メソッド(自分で実装する)を使い、シリアライズする。
>
> なお、Shared プロパティの初回アクセスがマルチスレッドで同時に起きることが予想される場合、インスタンス生成が多重に行われる可能性があります。
> 必要に応じて排他処理を考えてください。
■No29475に返信(ニャンコフさんの記事)
> 以下のコードを考えているのですが、気になるところがあればご指摘下さい。
> コードは一番下にあります。

一言で表す、シングルトンになっていません。
少なくとも、2 回生成されています。


> @ _Instance フィールドは、宣言文の場所で初期化( New )しています。
>  これは、マルチスレッドからアクセスされる場合でもインスタンス生成が多重に行われないようにするための対策になっていますでしょうか?

この件に関しては即答できるほど、私もきちっとは詰められていません。
何らかのドキュメント的な裏付けがあればよいのですが…。


> A この Instance プロパティからのみ、シングルトンインスタンスへのアクセスを認める形にしたつもりです。

このクラスの外から Set できるようですが、どうしてそのように書いているのでしょうか?
※デフォルトは Friend であるという認識に基づいていますが、間違っていたらすみません。


> B Shared コンストラクタです。この Shared コンストラクタ内で、
>   XML ファイルからデシリアライズしようと考えています。
>  このようにしておけば、宣言文での初期時(@)に、デシリアライズされるでしょうか?

静的コンストラクタを作る必要はないはずです。
シングルトンとはいえ、普通のコンストラクタで事足りるはずなので。


>  それとも、別途、Private の Sub New() を作っておかなければならないのでしょうか?

シングルトンのクラスは、外部から生成できないようにしておくべきです。
今は特に制限していないようなので、外部から生成できるので、シングルトンになりませんね。
Private のコンストラクタを用意することで、外部からの生成を(ある程度)抑制できます。


> C 読み込みメソッド Load() です。読み込み成功したら True を返します。
>   クラス外から呼び出されることがないので、Private にしています。

New でインスタンスを作って、Load でインスタンス作り直していますね。
この状況はシングルトンといえないと思います。

私の先の投稿でも「Shared プロパティでデシリアライズしてください」と書いているのに対して、違っていますよね?


>   Save メソッドは、これから考えようと思っています。アプリケーションの終了時に
>   呼び出したいので、Public で作るつもりです。

それで OK です。
というよりは、このクラス自身では Save タイミングを適切におけないので。


> D Using 句は使いなれないのですが、エラー処理は、これで大丈夫でしょうか? Using 句を使っているだけです。

「エラー処理」に何を含んでいるのでしょうか。
ファイルが万が一にも開いたままになることは防ぐかもしれませんが、例外がでたときはそのまま外部に例外が渡りますね。
この状態は「エラー処理として大丈夫」なのかどうか、第三者には判断できかねます。
(「エラー処理」がどういったことを示すのか、False を返すべき状況はどこまでなのかが定義されていない)


※引用は最小限にしていただけないでしょうか。使わない引用文は消しておくなど。
2011/12/12(Mon) 13:08:09 編集(投稿者)

■No29475に返信(ニャンコフさんの記事)
> ご教授ありがとうございます。
> 以下のコードを考えているのですが、気になるところがあればご指摘下さい。
参考記事として。VBでは無いですけれども。
http://msdn.microsoft.com/ja-jp/library/ms998558.aspx
http://codezine.jp/article/detail/141?p=2
http://www.tt.rim.or.jp/~rudyard/torii009.html


> _Instance フィールドは、宣言文の場所で初期化( New )しています。
>  これは、マルチスレッドからアクセスされる場合でもインスタンス生成が多重に行われないようにするための対策になっていますでしょうか?
(3)を使わずに既定のコンストラクタだけを使っているのであれば、
Shared フィールド宣言時に New すること自体は安全です。

ただ、それぞれのメソッドはスレッドセーフでは無いので、現時点では
この部分だけを見て、マルチスレッド対応を気にしても仕方ないと思います。
値を拾っている最中に、Load と Save が同時に行われた場合の対策等も
講じていないようですし、そもそも _Instance フィールドを ReadOnly に
しているわけでもなく、外部から Settings.Instance = Nothing も実行できますし。
2011/12/12(Mon) 20:31:04 編集(投稿者)

Azurean 様
コメントありがとうございます。

> 一言で表す、シングルトンになっていません。
> 少なくとも、2 回生成されています。

シングルトンについて、まだよくわかってなくてすみません。
Instance が、宣言文での初期化時と、Load() メソッド内のデシリアライズの合計 2 回生成されている、ということでしょうか。

> > @ _Instance フィールドは、宣言文の場所で初期化( New )しています。
> >  これは、マルチスレッドからアクセスされる場合でもインスタンス生成が多重に行われないように
> > するための対策になっていますでしょうか?
>
> この件に関しては即答できるほど、私もきちっとは詰められていません。
> 何らかのドキュメント的な裏付けがあればよいのですが…。

余計な質問をしてしまってすみませんでした。
今回は、シングルトンのクラスを XML シリアル・デシリアルすることが目的でしたので、
マルチスレッドの場合の対策については、別途きちんと調べてから、また考えたいと思います。

静的な初期化(宣言文の場所で初期化)ではなく、
Instance プロパティの Get で初期化(デシリアライズ)する形に変更します。
コンストラクタは、Private にします。

ご指摘下さった内容を反映しておらず、失礼しました。

これまで、私は最初の投稿にあるような、静的なプロパティ群のクラスをよく使っていました。
クラス内のメンバーへ初回アクセス時にのみ、呼び出される Shared コンストラクタの使い勝手が気に入っておりました。今回も、静的なプロパティ群のクラスと少々混同していました。

Instance プロパティが ReadOnly になっていなかったことについて、
特に理由はありませんでした。ReadOnly 指定をつけておきます。
スコープは、ひとまず Public Shared にします。
よくわからないまま、スコープを省略してしまって失礼しました。

また、エラー処理について、変な質問をしてしまってすみませんでした。
Using 句に慣れていなくて・・・エラー処理を行う場合は、Try Catch 文を併用しようと思います。
魔界の仮面弁士様

コメントありがとうございます。
いつも様々な場所で書き込みを拝見しています。
こうして直接コメントをもらったのは初めてで嬉しいです。

紹介して頂いた記事を読みました。(お気に入りに保存しました。)
日本語の指摘までして下さって、お恥ずかしい限りです。
丁寧な言葉遣いが慣れていないことがバレバレです・・・

紹介して下さった codezine のどぼん!様の記事内の
「シングルトンとダブルチェックロッキング」の部分で、「静的な初期化」が良いと
書かれていましたが、私の場合、ダブルチェックロッキングが何なのかわからないため、
この辺り(マルチスレッドプログラミングについて)もしっかり勉強して、
理解を深めていきたいと思います。

静的な初期化を行う場合は、私のコードのB部分は使わず、既定のコンストラクタだけを
使うべき、とのアドバイスも有難うございました。

いつも参考になる書き込み、本当にありがとうございます。
今回の疑問に限らず、ネット検索で書き込みを拝見し、
今までいろいろな疑問を解決して下さいました。
有難うございました。それでは失礼します。
2011/12/12(Mon) 20:36:29 編集(投稿者)

お詫び

一番上の私の発言 29469 で、URL の記述ミスがありました。

> アプリケーションの設定を XML シリアライズ・デシリアライズで保存する
> 記事(http://dobon.net/cgi-bin/pc/pc.php#post_comment)

記事(http://dobon.net/vb/dotnet/programing/storeappsettings.html)
でした。失礼しました。
この記事は何度も読んでいて、今回も最後まで読んだつもりになっていたのですが・・・
この記事の最後の「まとめ」のコード内容が、クラスにただ一つのインスタンスをシリアライズする形のコードになっており、
これを見つけた後、「しまった」と思ったことは内緒です・・・。

今回は、クラス内でデシリアライズの処理を自動で行いたかったので、少し違うといえば違いますが・・・。
読み落としていました。すみませんでした。


また、今回コメントして頂いて、今後の方針が定まり、また勉強しなければならない部分が明確になりました。ありがとうございました。
ひとまずここで解決済みとさせていただきます。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板