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 を返す」
ポイントはここです。
- 対象キーが存在する場合:
OpenSubKeyはRegistryKeyを返す - 対象キーが存在しない場合:
OpenSubKeyは null を返す
つまり、キーが無いとき Key には null が入ります。それにもかかわらず、最後に Key.Close() を実行すると、null に対してメソッド呼び出しを行うため NullReferenceException(参照が null)になります。
| 行 | やっていること | キーが存在する場合 | キーが存在しない場合 | 結果 |
|---|---|---|---|---|
OpenSubKey | サブキーを開く(読み取り) | RegistryKey が返る | null が返る | ここは例外にならないことが多い |
Close() | 開いたキーを閉じる | 正常に閉じる | null に Close() しようとして落ちる | NullReferenceException |
この「OpenSubKey が null を返す仕様」を知っているかどうかで、ハマりやすさが大きく変わります。
解決策(推奨):using で安全に破棄しながら存在有無を判定する
もっとも簡潔で、実務的にも事故が少ないのが using を使う方法です。RegistryKey は IDisposable を実装しているため、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 の戻り値で判定する
「キーがある=有効化済み」とは限りません。実務では、キーの下に値(例:ActivationState や LicenseKey)を保存し、その値があるか/妥当かを見たいケースが多いです。
値の存在確認は、キーを開いた後に 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_MACHINE) | PC単位 | 全ユーザー共通の設定に向く | 書き込み権限が厳しい/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() は何が違うのですか?
RegistryKey は IDisposable を実装しており、実務上は 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の切り分け」など要件が増えた際にもスムーズに拡張できます。

コメント