HIRASE CONNECTION WK

programming collection

目次

Blog 利用状況

ニュース

あわせて読みたいブログパーツ

書庫

日記カテゴリ

Link Collection

[C#] スタティックなメンバは、そのクラスが始めて使われるときに初期化されるという誤解でハマった8月のある日

C# にて、「スタティックなメンバは、そのクラスが始めて使われるときに初期化されるという誤解」をしていたという、ある自分のある8月の話。

次のようなコードで、不思議なことが起こります。

using System;
using System.Windows.Forms;

public class MyForm : Form
{
    private MyForm()
    {
        MessageBox.Show("Called \"MyForm.MyForm()\"");
    }

    static MyForm defaultForm = new MyForm();
    public static MyForm Default
    {
        get { return defaultForm; }
    }
}

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        MessageBox.Show("Do \"Application.Run(MyForm.Default);\"");
        Application.Run(MyForm.Default);
    }
}

スタティックなメンバの初期化タイミングに関する罠

上記のコードで、Program.Main() 中の Application.SetCompatibleTextRenderingDefault(false); ですが、このメソッドは、Form の初期化前に呼ぶ必要があります。Form を初期化してから呼び出すと、以下のような例外が起こります。

System.InvalidOperationException はハンドルされませんでした。
  Message="最初の IWin32Window オブジェクトがアプリケーションで作成される前に、SetCompatibleTextRenderingDefault が呼び出されなければなりません。"
  Source="System.Windows.Forms"
  StackTrace:
       場所 System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(Boolean defaultValue)
       場所 Program.Main() 場所 D:\CodeGagdet\TestStaticClass\TestStaticClass\Program.cs:行 25
       場所 System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       場所 System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       場所 Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       場所 System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       場所 System.Threading.ThreadHelper.ThreadStart()
  InnerException: 

上記のコードでも、きちんとこの順番を守って呼び出している「つもり」です。

しかし、よく見ると、MyForm は自身のインスタンスをスタティックメンバー defaultForm として持っています。

この defaultForm の初期化は、「MyForm が初めて使用される Application.Run() 時に行われる」ものだと思っていたら痛い目にあいました。。

実行条件によって変わるスタティックメンバーの初期化タイミング

実は、スタティックなメンバーの初期化タイミングは、実行条件によって変わっていました。

Debugビルド + デバッグあり実行
初めて MyForm が使用される MyForm.Defaul プロパティの呼び出し直前。
Debugビルド + デバッグなし実行
同上
Releaseビルド + デバッグあり実行
同上
Releaseビルド + デバッグなし実行
Program.Main関数に入ったとき。あるいは、その直前。

4つ目のケース「Releaseビルド + デバッグなし実行」では、defaultForm が Application.SetCompatibleTextRenderingDefault() より手前で初期化されます。

これでは、先に挙げた例外 InvalidOperationException が発生します。

もちろん、上手くいく系もあるでしょうし、逆に Debug モードでだって上手くいかない系もあるかと思います。

解決方法(追記@2008-10-03)

    static MyForm defaultForm;
    public static MyForm Default
    {
        get
        {
            if (defaultForm == null)
                defaultForm = new MyForm();
            return defaultForm;
        }
    }

スタティックなメンバーのインスタンス生成を自分が意図したタイミングまで引き伸ばしますことで解決しました。

コメント欄にもあるように、スタティックなコンストラクタを用意することでも解決するようですが、これもタイミングが本当に一定なのかがわからないという意味で、やめた方が良いでしょう。

まとめ

スタティックなメンバーの初期化は、いつ行われるかわかりません。

少なくとも初めてそのクラスが使われるまでには初期化されますが、今回のようなケースだとだいぶ痛い目に合います。

そんなこんなで、気をつけましょうね、という日記でした。

投稿日時 : 2008年10月2日 22:28

コメントを追加

# re: [C#] スタティックなメンバは、そのクラスが始めて使われるときに初期化されるという誤解でハマった8月のある日 2008/10/03 10:26 よねけん

こういった挙動はなかなか把握しづらいですね。
↓この辺が参考なりますね。
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=20749&forum=7&7

# re: [C#] スタティックなメンバは、そのクラスが始めて使われるときに初期化されるという誤解でハマった8月のある日 2008/10/03 22:53 Hirase

To: よねけんさま。
リンク先の検証、参考になります。

スタティックコンストラクタの有無で変わるのですね。
でも、この仕様が本当に仕様なのかも疑問ですね。
いつ変わるともわからない。

というわけで、今回のケースで、自分が行ったのは、
Defaultプロパティのget内で、defaultFormがnullだったらインスタンスを作ってから、returnするという修正を行いました。つまり↓。

static MyForm defaultForm;
public static MyForm Default
{
get
{
if (defaultForm == null)
defaultForm = new MyForm();
return defaultForm;
}
}





# 本文に解決方法を載せてなかった・・・。追記追記。

タイトル  
名前  
URL
コメント