C#でレジストリの読み書き:.NET 10のCA1416とRegistryView
C#のRegistryKeyでキーの作成・値の読み書き・削除を.NET 10で検証し直しました。CA1416警告とOperatingSystem.IsWindows()、32/64ビットのRegistryView、設定ファイルとの使い分けまで。
2026年11月改訂: この記事は2021年に韓国語で書いたレジストリの講座を、日本語向けに書き直したものです。.NET 10 で検証し直しましたが、レジストリのコードは Windows 専用のため、Windows向けに.NET 10でコンパイル済み、このマシン(Apple Silicon の Mac)では実行していません。 実行結果として載せているのは、macOS で実際に動かした出力(プラットフォーム判定、判定なしで起きる例外、代わりの保存先)だけで、Windows での動作は本文中で名前を挙げた Microsoft Learn のページに基づいて説明しています。2021年版の「整数を渡しても REG_SZ で保存される」という説明と
RegistryValueKindの表は誤りがあったので直しました。
C# から Windows のレジストリに値を書いたり読んだりしたい人、そして古いコードを .NET 10 に移したら CA1416 の警告が大量に出て困っている人向けの記事です。先に結論を書くと、RegistryKey の使い方は2021年からほとんど変わっていません。ただし今は、①キーは using で閉じる、②書く場所は HKCU\Software\会社名\アプリ名 の下、③Windows専用のコードだとコンパイラーに伝える、の3点が必要です。そして、アプリ自身しか読まない設定なら、レジストリより設定ファイルの方が向いている場面が多いです。
RegistryKey の基本:キーの作成・オープン・値の読み書き
レジストリのクラスは Microsoft.Win32 名前空間にあり、.NET 10 では追加の NuGet パッケージなしで使えます。よく使うメンバーは次のとおりです。
| メンバー | 役割 |
|---|---|
CreateSubKey |
サブキーを作る。既にあれば書き込み可能で開く |
OpenSubKey(name) / OpenSubKey(name, true) |
読み取り専用/書き込み可能で開く。なければ null |
DeleteSubKey / DeleteSubKeyTree |
サブキーを削除/子キーごと再帰的に削除 |
GetSubKeyNames、GetValueNames |
サブキー名・値の名前の一覧 |
SetValue、GetValue、GetValueKind、DeleteValue |
値の書き込み・読み取り・型の確認・削除 |
2021年版の例は HKEY_CURRENT_USER の直下に c# rkey というキーを作っていました。動きはしますが、HKCU の最上位は Windows や他のソフトが使う場所です。アプリのデータは慣例どおり HKCU\Software\<会社名>\<アプリ名> の下に置きます。2026年版のコードです。
using Microsoft.Win32;
using System.Runtime.Versioning;
[SupportedOSPlatform("windows")]
static class RegistryDemo
{
const string AppKey = @"Software\TLabSample";
public static void Run()
{
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(AppKey))
{
key.SetValue("Greeting", "hello"); // string -> REG_SZ
key.SetValue("Count", 1234); // int -> REG_DWORD
key.SetValue("Big", 1234L); // long -> REG_SZ (!)
key.SetValue("Big64", 1234L, RegistryValueKind.QWord);
foreach (string name in key.GetValueNames())
Console.WriteLine($"{name}: {key.GetValueKind(name)} = {key.GetValue(name)}");
}
using (RegistryKey? key = Registry.CurrentUser.OpenSubKey(AppKey)) // 読み取り専用
{
int count = key?.GetValue("Count") is int n ? n : 0;
string greeting = key?.GetValue("Greeting") as string ?? "(none)";
Console.WriteLine($"Count={count}, Greeting={greeting}");
}
Registry.CurrentUser.DeleteSubKeyTree(AppKey, throwOnMissingSubKey: false);
}
}
このコードは net10.0-windows と net10.0 のどちらでも警告0件でコンパイルできました。2021年版からの変更点は次のとおりです。
RegistryKeyは OS のハンドルを持っているのでusingで閉じます。2021年版のCreateSubKey("a").CreateSubKey("b")のようにつなげると、途中のキーが閉じられません。パスは@"a\b"とまとめて渡せます。OpenSubKeyはキーがないとnullを返すので、?.と型パターン(is int n)で受けて既定値を用意します。- 後片付けには
DeleteSubKeyTree(..., throwOnMissingSubKey: false)が無難です。Microsoft Learn のRegistryKey.DeleteSubKeyのページには、子キーが残っているとInvalidOperationException、キーがないとArgumentExceptionを投げると書かれています。
2021年版のコードを .NET 10 でビルドすると
2021年版の C# の断片をそのまま WinForms プロジェクト(net10.0-windows、テンプレート既定の Nullable 有効)に入れ、dotnet build -p:EnableWindowsTargeting=true でビルドしました。エラーはなく、nullable の警告が7件出ました。
Program.cs(23,32): warning CS8600: Null リテラルまたは Null の可能性がある値を Null 非許容型に変換しています。
Program.cs(23,32): warning CS8602: null 参照の可能性があるものの逆参照です。
Program.cs(26,32): warning CS8600: Null リテラルまたは Null の可能性がある値を Null 非許容型に変換しています。
Program.cs(38,32): warning CS8600: Null リテラルまたは Null の可能性がある値を Null 非許容型に変換しています。
Program.cs(39,29): warning CS8602: null 参照の可能性があるものの逆参照です。
Program.cs(42,32): warning CS8600: Null リテラルまたは Null の可能性がある値を Null 非許容型に変換しています。
Program.cs(43,13): warning CS8602: null 参照の可能性があるものの逆参照です。
23行目は Registry.CurrentUser.OpenSubKey("c# rkey").OpenSubKey("testsubkey")、39行目は MessageBox.Show(rkey.GetValue("test").ToString()) です。コンパイラーの指摘どおりで、キーや値がまだない PC では両方とも null が返るため、2021年版は初回起動時に NullReferenceException で落ちうるコードでした。上の2026年版は両方のケースを扱っていて警告もありません。
本文の説明にも直すところがありました。
- 「整数を渡しても文字列を渡しても REG_SZ で保存される」は誤りです。 Microsoft Learn の
RegistryKey.SetValueのページによると、型を指定しないオーバーロードはintをREG_DWORDで、それ以外の数値型(longを含む)を文字列で保存します。上のコードでBig(long)がREG_SZ、QWordを指定したBig64がREG_QWORDになるのはこのためです。 RegistryValueKindの表。 元の記事はメンバーを7つとし、UnknownをREG_RESOURCE_LISTと説明していました。.NET 10 で列挙値を実際に出力すると8つあります。
RegistryValueKind: Unknown=0, String=1, ExpandString=2, Binary=3, DWord=4, MultiString=7, QWord=11, None=-1
Unknown は「サポートされていない型」を表し、ほかに None(データ型なし)があります。また元の例の DeleteSubKey(@"c# rkey\qwd") は、コードのどこでも作っていないキーを消そうとしていました。まっさらな PC では先ほどの ArgumentException になります。
CA1416:「この呼び出しサイトはすべてのプラットフォームで到達可能です」
レジストリのコードを OS 指定なしの net10.0 のコンソールプロジェクトに入れると、ビルドは通りますが警告が付きます。判定なしで Registry.CurrentUser.CreateSubKey(...) と SetValue を呼んだときの実際の出力です。
Program.cs(10,5): warning CA1416: この呼び出しサイトはすべてのプラットフォームで到達可能です。'RegistryKey.SetValue(string?, object)' は 'windows' でのみサポートされています。
Program.cs(5,5): warning CA1416: この呼び出しサイトはすべてのプラットフォームで到達可能です。'RegistryValueKind' は 'windows' でのみサポートされています。
Program.cs(9,29): warning CA1416: この呼び出しサイトはすべてのプラットフォームで到達可能です。'Registry.CurrentUser' は 'windows' でのみサポートされています。
Program.cs(9,29): warning CA1416: この呼び出しサイトはすべてのプラットフォームで到達可能です。'RegistryKey.CreateSubKey(string)' は 'windows' でのみサポートされています。
警告を無視して macOS で実行してみました。
System.NullReferenceException: Object reference not set to an instance of an object.
予想した PlatformNotSupportedException ではありません。原因を確かめると、Windows 以外では Registry.CurrentUser そのものが null で、ベースキーを明示的に開く方はわかりやすい例外を投げました。
Registry.CurrentUser is null: True
OpenBaseKey: PlatformNotSupportedException: Registry is not supported on this platform.
CA1416 を正しく消す方法は3つです。
- ターゲットを
net10.0-windowsにする。 WinForms や WPF のアプリは最初からこれなので、この警告は出ません。 - 呼び出しを
if (OperatingSystem.IsWindows())で囲む。 アナライザーはこの判定を理解します。 - Windows 専用のメソッドやクラスに
[SupportedOSPlatform("windows")]を付ける。 上のRegistryDemoがこの形で、呼び出し側は2の判定を使います。
複数の OS で動くライブラリなら2と3の組み合わせがすっきりします。実際に使ったエントリポイントです。
if (OperatingSystem.IsWindows())
{
RegistryDemo.Run();
}
else
{
// 同じデータを、ユーザーごとのアプリデータフォルダーの JSON ファイルに
string dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "TLabSample");
Directory.CreateDirectory(dir);
string file = Path.Combine(dir, "settings.json");
File.WriteAllText(file, JsonSerializer.Serialize(new Settings("hello", 1234)));
Settings s = JsonSerializer.Deserialize<Settings>(File.ReadAllText(file))!;
Console.WriteLine($"Not Windows -> {Path.GetFileName(dir)}/settings.json: {File.ReadAllText(file)} Count={s.Count}");
}
record Settings(string Greeting, int Count);
macOS での出力です(ビルド時の CA1416 は0件)。
Not Windows -> TLabSample/settings.json: {"Greeting":"hello","Count":1234} Count=1234
32ビットと64ビット:RegistryView
64ビット Windows で32ビット(x86)のプロセスが HKLM\SOFTWARE を開くと、Windows が黙って HKLM\SOFTWARE\WOW6432Node に振り向けます。Microsoft Learn の「Registry Redirector」「Registry Keys Affected by WOW64」に書かれている動作で、「64ビットのインストーラーが書いた値が32ビットのツールから見えない」という相談はほぼこれが原因です。
プロセスのビット数に関係なく見る側を決めたいときは、RegistryKey.OpenBaseKey に RegistryView を渡します。
// 32ビットプロセスからでも64ビット側の HKLM を見る
using RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
using RegistryKey? cv = hklm.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
Console.WriteLine(cv?.GetValue("ProductName"));
RegistryView.Default は「このプロセスのビット数どおり」という意味です。AnyCPU の .NET 10 アプリは64ビット Windows では64ビットで動くので普段は気になりませんが、<PlatformTarget>x86</PlatformTarget> でビルドしたアプリや32ビットのプラグインホストでは差が出ます。このコードもほかの Windows 用コードと同じく、コンパイルのみで実行はしていません。
2026年にレジストリは必要か
2021年版は「設定の保存=レジストリ」という前提で書かれていました。今の判断は次の図のようになります。
- アプリ自身しか読まない設定:
%APPDATA%\<アプリ名>\settings.jsonのようなユーザープロファイル下のファイルが向いています。同じコードが macOS や Linux でも動き(上の実行結果)、バックアップや差分の確認がしやすく、サポート時に「このファイルを送ってください」と頼めます。サーバーアプリならappsettings.jsonとMicrosoft.Extensions.Configurationが定番です。 - Windows や他のプログラムが読む値:
HKCU\Software\Microsoft\Windows\CurrentVersion\Runへのスタートアップ登録、ファイルの関連付け、他アプリと取り決めたキーなどは、レジストリ以外に手段がありません。 - マシン全体の設定:
HKLMへの書き込みには管理者権限が要り、実行中のアプリが書こうとするとUnauthorizedAccessExceptionになります。インストーラーが書き、アプリは読むだけ、という分担が一般的です。 - MSIX でパッケージ化したアプリ:Microsoft Learn の MSIX 関連のドキュメントによると、パッケージアプリのレジストリ書き込みはパッケージ専用の場所に仮想化されます。他のプログラムに読ませるつもりなら先に確認してください。
まとめ:2021年版から変わったこと
| 2021年版 | 2026年 |
|---|---|
HKCU\c# rkey に直接キーを作成 |
HKCU\Software\<会社名>\<アプリ名> の下に |
| 整数も文字列も REG_SZ で保存 | int は REG_DWORD、long などは REG_SZ、QWord 指定で REG_QWORD |
RegistryValueKind は7つ、Unknown=REG_RESOURCE_LIST |
None を含めて8つ、Unknown は「サポート外の型」 |
| キーを閉じない、null を確認しない | using+?.、nullable 警告0件 |
| プラットフォームへの言及なし | net10.0-windows か OperatingSystem.IsWindows()(CA1416) |
| レジストリエディターのスクリーンショット7枚 | 削除(下記) |
2021年版にあったレジストリエディターのスクリーンショット7枚は外しました。個人情報は写っていませんでしたが、今はおすすめしない HKCU 最上位のキーを示す画面で、今回は Windows で実行していないため撮り直すこともできないからです。代わりに上の2つの図を入れています。
レジストリ操作の周りでどこまで try/catch するかは例外処理の記事で扱っています。WinForms アプリから設定画面を開く方法はモーダル・モードレスフォームを参照してください。
確認環境:.NET SDK 10.0.403/ランタイム 10.0.14、C# 14、Microsoft.WindowsDesktop.App.Ref 10.0.14、macOS(Apple Silicon の Mac)。Windows 専用のコードは dotnet build -p:EnableWindowsTargeting=true でコンパイルのみ行いました。
よくある質問
SetValue に int を渡すと REG_SZ(文字列)で保存される?
いいえ。2021年版の記事にはそう書いていましたが誤りでした。Microsoft Learn の RegistryKey.SetValue のページによると、RegistryValueKind を指定しないオーバーロードは int(32ビット整数)を REG_DWORD で、long などそれ以外の数値型は文字列(REG_SZ)で保存します。64ビット値を REG_QWORD にしたいときは SetValue(name, value, RegistryValueKind.QWord) を使います。
net10.0 のプロジェクトでレジストリを使うと CA1416 が出るのはなぜ?
レジストリのAPIには「windows でのみサポート」という印が付いているため、ターゲットフレームワークが OS 指定なしの net10.0 だとプラットフォーム互換性アナライザーが CA1416 を出します。net10.0-windows にする、if (OperatingSystem.IsWindows()) で囲む、メソッドやクラスに [SupportedOSPlatform("windows")] を付ける、のいずれかで消えます。警告を無視して macOS で動かすと Registry.CurrentUser が null で、NullReferenceException になりました。
32ビットのプログラムで HKLM\SOFTWARE の値が見つからないのは?
64ビットWindowsでは、32ビットプロセスが開いた HKLM\SOFTWARE は HKLM\SOFTWARE\WOW6432Node にリダイレクトされます(Microsoft Learn「Registry Redirector」)。RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) でベースキーを開けば、32ビットプロセスからでも64ビット側を読めます。