- PR -

開発環境とEXEとでstatic領域の初期化タイミングがちがう

1
投稿者投稿内容
じゃばら
会議室デビュー日: 2004/05/24
投稿数: 10
お住まい・勤務地: 福岡
投稿日時: 2005-04-20 11:10
こんにちは、じゃばらと申します。
VisualStudio.NET2003とC#を使って開発していて、開発環境とEXEにになったときで、クラスのstatic領域の初期化タイミングがちがうことに気が付きました。

状況は以下のようなものです。
・あるDLLに以下のようにシングルトンで実装されたクラスがある。

コード:
public class Piyo
{
    static readonly Piyo SINGLETON = new Piyo();
    private Piyo()
    {
        MessageBox.Show(this.GetType().Name + "のコンストラクタが動いた!");
    }
    public static Piyo Singleton
    {
        get { return SINGLETON; }
    }
    public void Print()
    {
        MessageBox.Show(this.GetType().Name + "のPrintメソッドが動いた!");
    }
}



・起動ポイントを持つクラスから、Singletonクラスを以下のように参照している。

コード:
public class Hoge
{
    [STAThread]
    static void Main() 
    {
        MessageBox.Show("HogeのMainが動いた!");
        Piyo.Singleton.Print();
    }
}



このような状況で、VS.NETから実行した場合は初めてSingletonクラスが参照されたタイミングでstatic変数が初期化されます。
つまり以下のような順序でメッセージボックスが表示されます。

「HogeのMainが動いた!」→「Piyoのコンストラクタが動いた!」→「PiyoのPrintメソッドが動いた!」

これが期待する動作ですし、一般的に説明されている動作だと思います。

しかし、ビルドしたEXEから実行すると、Singletonクラスが参照されるより先にSingletonクラスのstatic変数が初期化されてしまいます。
つまり以下のような順序でメッセージボックスが表示されます。

「Piyoのコンストラクタが動いた!」→「HogeのMainが動いた!」→「PiyoのPrintメソッドが動いた!」

なぜこのようなことが起こるのでしょうか?
また、どちらの挙動が正しい(共通言語ランタイムの仕様に則った、という意味で)のでしょうか?
じゃばら
会議室デビュー日: 2004/05/24
投稿数: 10
お住まい・勤務地: 福岡
投稿日時: 2005-04-20 11:17
じゃばら@スレッド作成者です。
補足を少々。

投稿内容からは分かりにくいのですが、シングルトンであるPiyoクラスと起動ポイントを持つHogeクラスは別のアセンブリ内にあります。
PiyoクラスはDLL、HogeクラスはEXE内にあります。

(まちがった情報だったので3行削除しました)

また、MSDNではシングルトンインスタンスの初期化をstatic変数の宣言と同時に行うパターンを「静的な初期化」として紹介しています。
http://www.microsoft.com/japan/msdn/practices/type/Patterns/enterprise/ImpSingletonInCsharp.asp

ここでは「この実装方針では、インスタンスは、クラスのいずれかのメンバが初めて参照されたときに作成されます。」と書かれているのですが・・・


[ メッセージ編集済み 編集者: じゃばら 編集日時 2005-04-20 17:19 ]

下記の3文を削除しました。
# ちなみにHogeクラスと同じアセンブリ内にあるクラスは
# 「初めて参照されるときに」static領域の初期化が行わ
# れます。
実際にはHogeクラスと同じアセンブリにあっても、異なるアセンブリにあるクラスと同様に動きます。

[ メッセージ編集済み 編集者: じゃばら 編集日時 2005-04-25 14:15 ]
karajan
ベテラン
会議室デビュー日: 2002/07/05
投稿数: 89
投稿日時: 2005-04-22 21:11
MSDNには、確かに初めて参照されたときに、云々・・・・とありますが(私もずっとそう理解してました)、ためしに起動時引数の有無によってシングルトンのクラスを参照するかどうかの条件分岐を書いてテストしたところ、参照されなくても(少なくともC#のコード上では)、プライベートコンストラクタが呼ばれてました。
たぶんですが、対象のシングルトンを参照しているメソッドが呼ばれた段階で、実際にそのメソッド内で、シングルトンが参照されなくても、先にインスタンスを生成してしまうようですね(わかりづらくてすみません)。
コンパイラの最適化と関連があるのかなぁ。
どなたか詳しい方がいましたら私もどのようになっているのか知りたいです。


[ メッセージ編集済み 編集者: karajan 編集日時 2005-04-22 21:11 ]

[ メッセージ編集済み 編集者: karajan 編集日時 2005-04-22 21:17 ]
壱丸3
常連さん
会議室デビュー日: 2004/09/13
投稿数: 34
投稿日時: 2005-04-23 00:41
コード:

(1)
static readonly Piyo SINGLETON = new Piyo();



の部分を

コード:

(2)
static readonly Piyo SINGLETON;
static Piyo() {
SINGLETON = new Piyo();
}



と変えてみると、

引用:

「HogeのMainが動いた!」→「Piyoのコンストラクタが動いた!」→「PiyoのPrintメソッドが動いた!」



の動きになるようです。

また、(1)と(2)のPiyoクラスを持つそれぞれのアセンブリを
ILDASMで開いて確認してみると、
(1)のアセンブリにはPiyoクラスに「beforefieldinit」フラグがついていますね。

以下が参考になりそうです。

http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/cpref/html/frlrfsystemreflectiontypeattributesclasstopic.asp

http://www.yoda.arachsys.com/csharp/beforefieldinit.html


[ メッセージ編集済み 編集者: 壱丸3 編集日時 2005-04-23 00:51 ]
karajan
ベテラン
会議室デビュー日: 2002/07/05
投稿数: 89
投稿日時: 2005-04-23 01:40
static コンストラクタをつけると 「beforefieldinit」属性がつかないで、本来期待している動作をするみたいですね。
先ほど一応確認してみました。

教えていただいたリンク先(2番目のMSDNじゃないほう)でも言っている様に、かなり多くの人がこのへんのことは知らずにいるのではないでしょうか?

C#の言語仕様を変更すべきかどうかはともかく、MSDNのドキュメントは確かに、その辺をちゃんと書くようにしてほしいと思います。

今回の挙動は正直かなり驚きでした。
じゃばら
会議室デビュー日: 2004/05/24
投稿数: 10
お住まい・勤務地: 福岡
投稿日時: 2005-04-25 14:13
じゃばらです。
返答ありがとうございます。
staticコンストラクタを使用した場合の挙動を私も確認しました。
今回の件の対処として、壱丸3さんのご指摘の通り、staticコンストラクタにてシングルトンインスタンスを初期化することにします。

# ほんとうはシングルトンの実装は、最もシンプルな静的な初期化が
# よいと思うのですが仕方がありません・・・(泣)

beforefieldinitフラグの存在を初めて知りました。勉強になります。
壱丸3さんに教えていただいたWebページを見ましたところ、英語力が足りなくて詳細は理解できなかったものの、
「beforefieldinitフラグのついたクラスのstaticメンバの初期化タイミングはけっこう変わるよ」
といったことが書いてあるように読みました。
アセンブリのロードのタイミングでクラスのstaticメンバの初期化が行われる、というのは仕様・・・ということになるのでしょうか?

ここのところを完全に理解するにはCLRの仕様を理解する必要がありそうです。
もう少し調べてみます。

ただ、いずれにしろVisualtStudio.NETにはもっとしっかり動作してほしいですね・・・
karajan
ベテラン
会議室デビュー日: 2002/07/05
投稿数: 89
投稿日時: 2005-04-25 14:23
一応念のため、静的コンストラクタ内で初期化するかどうかではなく、静的コンストラクタがあるかどうかで、挙動が変わるということです。

====コード1========
static MySingleton _value = new MySingleton();
static MySingleton()
{
}

====コード2=======
static MySingleton _value;
static MySingleton()
{
_value = new MySingleton();
}

例の英語のほうのサイトにもあったようにコード1と2は同じです。

一応念のためです。

[ メッセージ編集済み 編集者: karajan 編集日時 2005-04-25 14:25 ]
じゃばら
会議室デビュー日: 2004/05/24
投稿数: 10
お住まい・勤務地: 福岡
投稿日時: 2005-04-25 16:59
私、下記を読み取れていませんでした。
英語力付けなきゃ、ですね。
karajanさん、ご指摘ありがとうございました。
引用:

====コード1========
static MySingleton _value = new MySingleton();
static MySingleton()
{
}

====コード2=======
static MySingleton _value;
static MySingleton()
{
_value = new MySingleton();
}

1

スキルアップ/キャリアアップ(JOB@IT)