Windows Forms(C#)でレジストリキーの有無を判定する方法|OpenSubKeyがnullで例外になる原因と対策

Windows Forms(C#)アプリの起動時に「レジストリキーがあれば有効化済み」と判定したいのに、キーが無いだけで例外が発生して落ちてしまう――このトラブルは原因が分かれば数行で解決できます。OpenSubKey の戻り値と安全な破棄(using/nullチェック)を軸に、実務で困らない堅牢な書き方までまとめます。

目次

症状:レジストリキーが存在しない環境だけで例外になり、起動時に落ちる

Windows Forms(C#)で、起動時(Form1_Load)にレジストリ HKEY_CURRENT_USER\SOFTWARE\MyApp の有無を見て、Activated_Bool(有効化フラグ)を切り替える実装はよくあります。

ところが「キーが存在するPCでは動くのに、存在しないPCだけ例外で落ちる」ケースが発生します。典型的には次のようなコードです。

using Microsoft.Win32;

// 例:Form1_Load で判定したい
private void Form1_Load(object sender, EventArgs e)
{
    RegistryKey Key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\MyApp");

    if (Key != null)
    {
        Activated_Bool = true;
    }
    else
    {
        Activated_Bool = false;
    }

    Key.Close(); // ← キーが無いときに例外になりがち
}

一見すると「OpenSubKey が例外を投げている」ように見えるかもしれませんが、多くの場合は Close() を呼んだ瞬間に落ちています。

原因:OpenSubKey は「存在しないと例外」ではなく「null を返す」

ポイントはここです。

  • 対象キーが存在する場合:OpenSubKeyRegistryKey を返す
  • 対象キーが存在しない場合:OpenSubKeynull を返す

つまり、キーが無いとき Key には null が入ります。それにもかかわらず、最後に Key.Close() を実行すると、null に対してメソッド呼び出しを行うため NullReferenceException(参照が null)になります。

やっていることキーが存在する場合キーが存在しない場合結果
OpenSubKeyサブキーを開く(読み取り)RegistryKey が返るnull が返るここは例外にならないことが多い
Close()開いたキーを閉じる正常に閉じるnull に Close() しようとして落ちるNullReferenceException

この「OpenSubKey が null を返す仕様」を知っているかどうかで、ハマりやすさが大きく変わります。

解決策(推奨):using で安全に破棄しながら存在有無を判定する

もっとも簡潔で、実務的にも事故が少ないのが using を使う方法です。RegistryKeyIDisposable を実装しているため、using によって確実に破棄できます。さらに重要なのは、式の結果が null でも using は安全に動く点です(null の場合は Dispose が呼ばれません)。

using Microsoft.Win32;

private void Form1_Load(object sender, EventArgs e)
{
    using (var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\MyApp"))
    {
        Activated_Bool = (key != null);
    }
}

この形にしておけば、次のメリットがあります。

  • Close() の書き忘れ・呼び忘れが起きない
  • null のとき Close() を呼んで落ちる事故が起きない
  • 途中で return しても finally 相当で破棄されるため、例外や分岐が増えても壊れにくい

なお、C# 8 以降が使える環境なら、より短い using var も選べます(チームのコーディング規約に合わせてください)。

private void Form1_Load(object sender, EventArgs e)
{
    using var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\MyApp");
    Activated_Bool = (key != null);
}

解決策:null チェックしてから Close() する(手動管理する場合)

何らかの理由で using を使わず手動で閉じたい場合は、Close() の前に null チェックを入れます。C# の null 条件演算子(?.)を使うと簡潔です。

using Microsoft.Win32;

private void Form1_Load(object sender, EventArgs e)
{
    var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\MyApp");
    Activated_Bool = (key != null);
    key?.Close(); // key が null のときは Close() が呼ばれない
}

もちろん古典的に if (key != null) key.Close(); でも構いません。ただ、分岐や return が増えるほど「Close し忘れ」や「例外経路でCloseされない」リスクが出るため、基本は using を推奨します。

「キーがあるか」だけなら最短でこう書く(実務向けの整理)

同じ判定が複数箇所に散らばると、バグが混入しやすくなります。存在確認は小さな関数にまとめると保守性が上がります。

using Microsoft.Win32;

private const string MyAppSubKeyPath = @"SOFTWARE\MyApp";

private static bool ExistsMyAppKeyInCurrentUser()
{
    using (var key = Registry.CurrentUser.OpenSubKey(MyAppSubKeyPath))
    {
        return key != null;
    }
}

呼び出し側(Form1_Load)は次のようにスッキリします。

private void Form1_Load(object sender, EventArgs e)
{
    Activated_Bool = ExistsMyAppKeyInCurrentUser();
    ApplyActivationStateToUi();
}

private void ApplyActivationStateToUi()
{
    // 例:ボタンの有効/無効、表示メッセージの切り替えなど
    // btnProFeature.Enabled = Activated_Bool;
}

UI 更新(表示やEnabledの切替)と、判定ロジック(レジストリを読む)を分離すると、後から条件が増えても破綻しづらくなります。

値の存在確認までしたい場合:GetValue の戻り値で判定する

「キーがある=有効化済み」とは限りません。実務では、キーの下に値(例:ActivationStateLicenseKey)を保存し、その値があるか/妥当かを見たいケースが多いです。

値の存在確認は、キーを開いた後に GetValue("値名")null かどうかで判定できます。

using Microsoft.Win32;

private const string MyAppSubKeyPath = @"SOFTWARE\MyApp";
private const string ActivationValueName = "Activated"; // 例

private static bool IsActivatedByRegistryValue()
{
    using (var key = Registry.CurrentUser.OpenSubKey(MyAppSubKeyPath))
    {
        if (key == null) return false;

        var value = key.GetValue(ActivationValueName);
        if (value == null) return false; // 値が存在しない

        // 保存形式に合わせて解釈する(例:DWORD 0/1、または文字列 "true"/"false")
        if (value is int dword)
        {
            return dword != 0;
        }

        if (value is string s)
        {
            return bool.TryParse(s, out var b) && b;
        }

        // 想定外の型なら false 扱い(またはログ)
        return false;
    }
}

ここでのポイントは「値が存在しない」と「値があるが形式が想定外」を分けて扱えることです。ライセンス周りの不具合調査では、後者が意外と多く発生します(過去バージョンの形式が残る、手動編集される、など)。

実務的ポイント:権限や壊れた環境では例外が起きる可能性がある

「キーが無い」だけなら OpenSubKey は null を返すことが多いですが、実運用では次のような要因で例外が起きることがあります。

  • セキュリティ制限(ポリシー、企業端末の制御など)
  • 書き込み権限が必要な開き方をしている(writable=true で開いている等)
  • レジストリの状態が壊れている/I/O エラー相当
  • パス文字列が不正(空文字、無効文字など)

よく遭遇する例外と対処の考え方を整理すると、次のようになります。

例外(代表例)起きやすい原因現場での対処方針ユーザー影響を抑えるコツ
UnauthorizedAccessExceptionアクセス権不足(読み取り/書き込み)読み取りだけなら writable=false を徹底。必要なら権限の設計見直し「有効化されていない」扱いで継続しつつ、ログに残す
SecurityExceptionセキュリティポリシー/部分信頼など環境依存のため try-catch で吸収し、代替保存先も検討起動不能にしない(例:機能制限モードで起動)
IOExceptionレジストリ I/O エラー、破損など再試行よりもログ+復旧手順提示が現実的例外を握り潰さず、サポート向け情報を残す
ArgumentExceptionパスや値名が不正定数化、ユニットテスト、入力値の検証ユーザー端末依存ではないので、開発段階で潰す

堅牢性を上げたい場合は、判定関数に try-catch を追加します。「起動時に落ちない」ことが最優先なら、例外時は false 扱いで継続する設計が現場ではよく採用されます。

using System;
using Microsoft.Win32;

private const string MyAppSubKeyPath = @"SOFTWARE\MyApp";

private static bool SafeExistsMyAppKeyInCurrentUser()
{
    try
    {
        using (var key = Registry.CurrentUser.OpenSubKey(MyAppSubKeyPath))
        {
            return key != null;
        }
    }
    catch (UnauthorizedAccessException)
    {
        // 読み取り権限がない → 有効化されていない扱い(必要ならログ)
        return false;
    }
    catch (System.Security.SecurityException)
    {
        // ポリシーなどでアクセス不可 → 同上
        return false;
    }
    catch (Exception)
    {
        // 予期しない例外。起動不能を避けるなら false とし、別途ログ推奨
        return false;
    }
}

補足として、例外をすべて握り潰すのは調査性を下げるため、実際の運用ではログ出力(ファイル、Windows Event Log、アプリ内ログなど)を併用するのがおすすめです。

HKCU と HKLM の違い:保存場所の設計で「権限問題」を避ける

今回の例は HKEY_CURRENT_USER(HKCU)です。HKCU は「ログオンユーザーごとの領域」で、通常はアプリが読み書きしやすく、権限トラブルが比較的少ないのが利点です。

一方で、アプリ設計によっては HKEY_LOCAL_MACHINE(HKLM)に保存したい場合もあります(PC全体で共有したい設定など)。しかし HKLM は書き込みに管理者権限が必要になりやすく、企業端末ではさらに制限されることもあります。

保存先対象メリット注意点
HKCU(HKEY_CURRENT_USERユーザー単位権限問題が起きにくい/ユーザーごとに設定分離できる別ユーザーでログオンすると状態が変わる(別ユーザーでは未設定)
HKLM(HKEY_LOCAL_MACHINEPC単位全ユーザー共通の設定に向く書き込み権限が厳しい/64bit環境でのビュー差に注意

「有効化フラグ」をどこに置くべきかは、ユーザー単位の有効化なのか、PC単位の有効化なのかで設計が変わります。まずは要件を明確にし、それに合わせてレジストリのハイブを選ぶとトラブルが減ります。

64bit/32bit の落とし穴:HKLM を読むなら RegistryView も意識する

今回のキーは HKCU ですが、もし将来 HKLM の SOFTWARE 配下を参照する設計に変える場合、64bit Windows では 32bit アプリ/64bit アプリで見える場所が分かれることがあります(いわゆる WOW64 の影響)。

「regedit で見た場所にあるのに、アプリから読むと見つからない」という現象が出たら、次のように RegistryView を指定して読む方法が役に立ちます。

using Microsoft.Win32;

private static bool ExistsKeyInLocalMachine64View(string subKeyPath)
{
    using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
    using (var key = baseKey.OpenSubKey(subKeyPath))
    {
        return key != null;
    }
}

// 例:HKLM\SOFTWARE\MyCompany\MyApp を64bitビューで確認
// var exists = ExistsKeyInLocalMachine64View(@"SOFTWARE\MyCompany\MyApp");

運用中に「Any CPU でビルドしている」「x86 固定で配布している」などの事情があると、ここが原因で検出に失敗することがあります。キーが見つからない時は、まず “どのハイブ/どのビューを見ているか” を切り分けるのが近道です。

やってはいけない例:CreateSubKey で存在確認してしまう

存在確認のために CreateSubKey を使うのは避けましょう。これは「無ければ作る」動作なので、存在確認のつもりが副作用(キー生成)を起こし、次回以降の挙動を変えてしまいます。

// 非推奨:存在確認のつもりが、キーを作ってしまう
using (var key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\MyApp"))
{
    Activated_Bool = (key != null); // ほぼ常に true になってしまう
}

「設定を初期化するために無ければ作りたい」なら CreateSubKey が正解ですが、「有効化済みかどうかを判定する」用途では、まず OpenSubKey で読み取り、必要な時だけ作るという方針が安全です。

より読みやすい命名・実装のコツ(バグ予防)

今回の例では Activated_Bool という変数名が登場しました。動くかどうかとは別に、C# では bool の命名を揃えておくと、条件分岐の読み間違いが減ります。

  • 推奨パターン:isActivated, _isActivated, IsActivated(プロパティ)など
  • 避けたいパターン:Activated_Bool のように型を名前に含める(Hungarian 的)

例として、プロパティ化すると UI 側が読みやすくなります。

private bool IsActivated { get; set; }

private void Form1_Load(object sender, EventArgs e)
{
    IsActivated = SafeExistsMyAppKeyInCurrentUser();
    btnProFeature.Enabled = IsActivated;
}

こうしておくと、後で「キー+値+期限」など条件が増えても、判定ロジックを SafeExistsMyAppKeyInCurrentUser() 側に寄せられます。

動作確認の手順(テストしやすい形にする)

「キーがある場合」「キーが無い場合」を手早く切り替えて検証したいときは、テスト用にキーを作成・削除して挙動を見るのが確実です。Windows のコマンド(reg.exe)を使うと切り替えが簡単です。

:: キーを作成(テスト用)
reg add "HKCU\SOFTWARE\MyApp" /f

:: キーを削除(テスト用)
reg delete "HKCU\SOFTWARE\MyApp" /f

注意点として、レジストリ操作は誤ると別の設定に影響する可能性があります。検証用のパス(SOFTWARE\MyApp など)に限定し、運用端末では慎重に扱ってください。

よくある質問(Windows Forms/C#/レジストリ判定)

OpenSubKey が「キーが無いだけ」で例外を出すことはありますか?

一般的には、キーが存在しない場合は null を返す動作が多いです。ただし、権限やポリシーなどの環境要因が絡むと例外になる可能性があるため、アプリ要件(落として良いか/落とせないか)に応じて try-catch を検討してください。

Close() と Dispose() は何が違うのですか?

RegistryKeyIDisposable を実装しており、実務上は using で Dispose を確実に呼ぶのが推奨です。Close() を手動で呼ぶより、using の方が分岐や例外が増えても安全です。

Form1_Load でやっても大丈夫ですか?

キーの読み取りは軽量なことが多く、Form1_Load での判定は一般的に問題になりにくいです。ただし、起動時に複数の外部リソース(ネットワーク、ファイル、DBなど)も読む設計の場合は、起動が重くなるため処理の切り分けを検討してください。レジストリ判定自体は「最初に状態を決める」用途に向いています。

そもそも有効化情報をレジストリに置くのは安全ですか?

レジストリは「設定の置き場所」として便利ですが、改ざん耐性が高いわけではありません。有効化やライセンスを厳密に守りたい場合は、レジストリ単体に依存せず、署名付きデータやサーバー検証など別の仕組みと組み合わせる設計が必要です。一方で「社内ツールの機能ON/OFF」など、目的が設定管理ならレジストリは有力な選択肢です。

まとめ:例外の正体は「null に Close()」で、using で最短解決できる

Windows Forms(C#)で「レジストリキーの有無を判定したいが、存在しないと例外になる」問題は、原因が OpenSubKey が null を返すのに、null のまま Close() を呼ぶことにあります。

  • 最もおすすめ:using で OpenSubKey の結果を受け、key != null で判定する
  • 手動管理するなら:key?.Close() などで null 安全にする
  • 実運用では:権限・ポリシー・例外も考慮し、必要なら try-catch+ログを用意する

このパターンを押さえておくと、起動時のクラッシュを確実に防げるだけでなく、将来「値の検証」「期限チェック」「64bit/32bitの切り分け」など要件が増えた際にもスムーズに拡張できます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次