WinFormsでWindows.Storage.ApplicationData.Current.LocalSettingsがInvalidOperationExceptionになる原因と対処法【Windows 11/.NET 8】

Windows 11 と .NET 8 で C# WinForms アプリを開発していて、UWP/WinUI のコードを流用しようとして Windows.Storage.ApplicationData.Current.LocalSettings を呼び出した結果、起動直後に InvalidOperationException が発生してしまう……という相談が増えています。本記事では、この例外の正体と原因を丁寧に整理しつつ、WinForms で「正しく・安全に」ユーザー設定を保存するためのパターンを、具体的なコード例とともに解説します。

目次

WinForms で ApplicationData.Current.LocalSettings を呼ぶと例外になる現象

今回の現象は、Windows 11/.NET 8/C# 11/Visual Studio 2022 で作成した 通常の Windows Forms(WinForms)アプリ で、起動時に次のコードを実行すると発生します。


var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;

この 1 行を実行した瞬間に、以下のような例外がスローされます。

  • 例外の種類:InvalidOperationException
  • メッセージ:Operation is not valid due to the current state of the object.

一見すると「オブジェクトの状態が不正」としか書かれておらず、何が悪いのか分かりづらいメッセージです。ですが、原因はシンプルで、API が想定しているアプリの種類と、実際のアプリの種類が一致していない ことにあります。

原因:Windows.Storage.ApplicationData は UWP/WinUI・MSIX アプリ専用

まず押さえておきたいのが、Windows.Storage.ApplicationData は WinRT(Windows Runtime)の API であり、UWP/WinUI や Windows App SDK などの「パッケージ化アプリ」向けに設計された機能 だという点です。

具体的には、ApplicationData.Current は内部的に「アプリのパッケージ ID(Package Identity)」を前提として動作します。しかし、従来型の WinForms(非 MSIX、非パッケージ)アプリにはパッケージ ID が存在しません。そのため、WinForms から ApplicationData.Current にアクセスすると、API 側が「想定している状態ではない」と判断し、InvalidOperationException を投げてしまうわけです。

アプリ種別例パッケージ IDApplicationData.Current 利用可否
UWP アプリWindows 10 ストアアプリあり(必須)◯ 利用可能
WinUI 3 / Windows App SDK(MSIX)新しいデスクトップ アプリあり(MSIX パッケージ)◯ 利用可能
通常の WinForms / WPF(非パッケージ).exe をそのまま配布するアプリなし× 利用不可(例外になる)

つまり、今回のエラーは「WinForms のコードが間違っている」というより、そもそもその API を使える環境ではない ことが原因です。この前提を踏まえたうえで、WinForms で推奨される設定保存の方法に切り替えていく必要があります。

WinForms でユーザー設定を扱う「正解」の選択肢

WinForms アプリでユーザー設定を保存・読み込みする方法として、代表的な選択肢は次の 3 つです。

解決策概要メリットデメリット/注意点
解決策 A
Properties.Settings
VS が自動生成する「型付き設定」を利用標準機能・型安全・手軽・ツールサポートあり細かい制御がしづらい場面もある
解決策 B
独自 JSON ファイル
settings.json などに自前で保存構造を自由に設計できる/他言語でも読みやすいクラス定義・シリアライズ処理を自分で書く必要
解決策 C
MSIX パッケージ化
WinForms をパッケージ化して WinRT API を利用UWP/WinUI と近い API 群を利用可能配布形態が変わる・パッケージングの学習コスト

一般的な業務アプリやツールであれば、まずは解決策 A:Properties.Settings の活用を検討する ことを強くおすすめします。理由は:

  • Visual Studio が UI 付きで設定項目を管理できる
  • 型安全にアクセスでき、リファクタリングしやすい
  • .NET の標準的な方法であり、情報量が多い

以下では、それぞれの方法をもう少し掘り下げて解説します。

解決策 A:Properties.Settings(型付き設定)を使う

Settings.settings にユーザー設定を定義する

まずは Visual Studio の標準機能である 型付き設定(Properties.Settings) を利用する手順です。

  1. ソリューション エクスプローラーでプロジェクトを選択
  2. メニューから「プロジェクト」→「プロパティ」を開く
  3. 左側の「設定」タブ(Settings.settings)を開く
  4. 以下のような項目を追加し、「範囲」は ユーザー にする
    • MaxGridSize(int)
    • MinGridSize(int)
    • GridSize(int)
    • ImageGridVisible(bool)
    • SnapTo(bool)
    • Upgraded(bool) ※後述のバージョンアップ用フラグ

これだけで、Properties.Settings.Default.MaxGridSize のように、型安全なプロパティとして設定が利用できるようになります。

起動時に設定を読み込むコード例

フォームの Load イベントなどで、設定を読み込んで画面や内部変数に反映します。ついでに、バージョンアップ時の設定引き継ぎもここで行うのがおすすめです。


private void MainForm_Load(object sender, EventArgs e)
{
    // 初回起動時に、旧バージョンの user.config から値を引き継ぐ
    if (!Properties.Settings.Default.Upgraded)
    {
        Properties.Settings.Default.Upgrade();   // 旧バージョンの設定をコピー
        Properties.Settings.Default.Upgraded = true;
        Properties.Settings.Default.Save();      // フラグを永続化
    }

    // 設定の読み込み
    MaxGridSize      = Properties.Settings.Default.MaxGridSize;
    MinGridSize      = Properties.Settings.Default.MinGridSize;
    GridSize         = Properties.Settings.Default.GridSize;
    ImageGridVisible = Properties.Settings.Default.ImageGridVisible;
    SnapTo           = Properties.Settings.Default.SnapTo;

    // 読み込んだ値を UI に反映(例)
    numericUpDownGridSize.Maximum   = MaxGridSize;
    numericUpDownGridSize.Minimum   = MinGridSize;
    numericUpDownGridSize.Value     = GridSize;
    checkBoxImageGrid.Checked       = ImageGridVisible;
    checkBoxSnapTo.Checked          = SnapTo;
}

このように、Properties.Settings.Default から読み込んだ値を、そのままフォーム上のコントロールに流し込むだけで、ユーザー設定の「復元」が実現できます。

終了時に変更を保存するコード例

設定を保存するタイミングとしては、フォームの FormClosing イベント で行うのが分かりやすく、典型的です。


private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    // 現在の UI の状態を Settings に書き戻す
    Properties.Settings.Default.MaxGridSize      = (int)numericUpDownGridSize.Maximum;
    Properties.Settings.Default.MinGridSize      = (int)numericUpDownGridSize.Minimum;
    Properties.Settings.Default.GridSize         = (int)numericUpDownGridSize.Value;
    Properties.Settings.Default.ImageGridVisible = checkBoxImageGrid.Checked;
    Properties.Settings.Default.SnapTo           = checkBoxSnapTo.Checked;

    // ディスクに保存
    Properties.Settings.Default.Save();
}

これで、アプリを終了して再度起動しても、前回の状態がそのまま復元されるようになります。

App.config と user.config の関係を整理する

ここでよく誤解されるのが、「設定の変更が App.config に書き戻されない」という点です。実際には、.NET の設定システムは次のように役割分担しています。

ファイル主な役割保存される値配置場所
App.config
(ビルド後は appname.exe.config)
アプリ全体の既定値・構成Settings で定義したデフォルト値アプリの実行ファイルと同じフォルダー
user.configユーザーごとの変更内容実行時にユーザーが変更した設定値%LOCALAPPDATA% 配下の専用フォルダー

つまり、ユーザー範囲の設定は App.config には二度と書き戻されない のが「仕様」です。
「App.config に保存されていないからおかしい」のではなく、「user.config に保存されるのが正しい」挙動だと理解しておきましょう。

ありがちな落とし穴(Properties.Settings 編)

  • 型を変えたのに、古い値が残って例外になる
    例:string だった設定を int に変更した場合、既存の user.config の値と整合が取れず例外になることがあります。
    対策:Upgrade() を使う/問題の設定を一旦削除して作り直す/古い user.config を削除するなど。
  • 管理者として起動したら設定が初期化されたように見える
    実際にはユーザー プロファイルが変わっているため、user.config の保存先が別になっているだけです。
    対策:一般ユーザーでの利用を前提とする/必要ならば「設定のエクスポート/インポート機能」を用意する。
  • 設定ファイルを直接コピーして配布しようとする
    user.config はユーザーフォルダー内の深い場所にあり、バージョンごとにパスも変わるため、手作業でコピーするのはおすすめできません。
    対策:アプリ内でバックアップ・復元機能を設ける方が安全です。

解決策 B:独自 JSON ファイルで設定を管理する

「既存のコードやサーバー側と JSON でやり取りしたい」「設定の構造が複雑で、Properties.Settings では扱いにくい」場合は、JSON ファイルで独自の設定クラスを定義する方法 が有力な選択肢になります。

設定クラスを定義する

まずは設定内容を表す C# クラスを用意します。初期値(デフォルト値)もここに記述しておくと分かりやすくなります。


using System.Text.Json;

public class AppSettings
{
    public int  MaxGridSize { get; set; } = 50;
    public int  MinGridSize { get; set; } = 1;
    public int  GridSize    { get; set; } = 50;
    public bool ImageGridVisible { get; set; } = false;
    public bool SnapTo           { get; set; } = false;
}

保存先フォルダー(パス)を決める

設定ファイルは、ユーザーごとに書き込み権限がある %LOCALAPPDATA%(ローカル環境用)や %APPDATA%(Roaming プロファイル)以下に置くのが定石です。ここでは %LOCALAPPDATA% 配下に YourCompany\YourApp フォルダーを作る例を示します。


string dir = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    "YourCompany",
    "YourApp");

Directory.CreateDirectory(dir); // 存在しなければ作成

string file = Path.Combine(dir, "settings.json");

JSON から読み込むコード

アプリ起動時に JSON ファイルがあれば読み込み、なければデフォルト値で初期化します。


AppSettings settings;

if (File.Exists(file))
{
    string json = File.ReadAllText(file);
    settings = JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
}
else
{
    settings = new AppSettings();
}

// 読み込んだ設定をアプリ側に適用
MaxGridSize      = settings.MaxGridSize;
MinGridSize      = settings.MinGridSize;
GridSize         = settings.GridSize;
ImageGridVisible = settings.ImageGridVisible;
SnapTo           = settings.SnapTo;

JSON へ保存するコード

アプリ終了時や「OK」ボタン押下時など、好きなタイミングで JSON に書き出します。


var settingsToSave = new AppSettings
{
    MaxGridSize      = MaxGridSize,
    MinGridSize      = MinGridSize,
    GridSize         = GridSize,
    ImageGridVisible = ImageGridVisible,
    SnapTo           = SnapTo
};

var options = new JsonSerializerOptions
{
    WriteIndented = true  // 見やすい整形 JSON にする
};

string jsonOut = JsonSerializer.Serialize(settingsToSave, options);
File.WriteAllText(file, jsonOut);

これで、例えば settings.json を他の端末にコピーして設定を移行するといった運用も簡単に行えます。Web アプリや他の言語と設定を共有したい場合にも有利です。

Properties.Settings との比較

項目Properties.Settings独自 JSON
手軽さ◎(VS の UI で設定)◯(コード記述が必要)
柔軟性△(階層構造などは苦手)◎(自由な構造が取れる)
他環境からの読み書き△(.NET 依存)◎(JSON なら多言語対応しやすい)
学習コスト低いやや高い(シリアライズの理解が必要)

小さめのツールや社内アプリであれば Properties.Settings、将来的に他システム連携を考えている場合は JSON といったように、アプリの性質に合わせて選択するとよいでしょう。

解決策 C:どうしても Windows.Storage を使いたい場合(MSIX パッケージ化)

「UWP アプリから WinForms に移植したが、ApplicationData.Current.LocalSettings をほぼそのまま使いたい」「既存の WinRT API をフル活用したい」といった事情がある場合は、WinForms アプリを MSIX でパッケージ化 し、アプリにパッケージ ID を与えることで Windows.Storage を利用できるようになります。

パッケージ化の大まかな流れ

Visual Studio 2022 の場合、代表的な手順は次のようになります。

  1. ソリューションに「Windows アプリケーション パッケージ プロジェクト」を追加する
  2. パッケージ プロジェクトから、既存の WinForms プロジェクトを参照として追加する
  3. パッケージ プロジェクトをスタートアップ プロジェクトに設定する
  4. アプリケーション マニフェストでパッケージ名や発行者情報を設定する
  5. MSIX パッケージとしてビルド/署名し、配布する

この形態で実行された WinForms アプリは、「パッケージ化されたデスクトップ アプリ」 として扱われるようになり、ApplicationData.Current を含む多くの WinRT API が利用可能になります。

ただし、この方法は以下のような注意点もあります。

  • 配布方法が「MSIX インストーラー」前提になる(従来の単体 .exe 配布とは変わる)
  • 企業内での配布の場合、MSIX の展開ルールや証明書管理などを理解する必要がある
  • 一部の API は依然として UWP 専用だったり、パーミッション設定が必要だったりする

そのため、単に「設定を保存したい」という目的だけであれば、無理に MSIX 化するよりも Properties.Settings や JSON を使う方が、開発・運用ともにシンプル なことが多いです。

移行パターン別の考え方

最後に、よくあるシナリオ別に「どの選択肢を取るべきか」をざっくり整理してみます。

UWP/WinUI アプリから WinForms へ移植中の場合

  • ApplicationData.Current.LocalSettings を使っていた部分は、WinForms では Properties.Settings または JSON に置き換える のが基本。
  • 設定キーの名前はそのまま流用しつつ、中身を C# のプロパティにマッピングすることで、移植の負担を減らせる。
  • 将来的にも WinRT API を多用したい場合のみ、MSIX パッケージ化を検討する価値がある。

既存の WinForms アプリに、あとから設定機能を追加したい場合

  • 特にこだわりがなければ、まずは Properties.Settings から始める のが圧倒的に楽。
  • 複数のフォームで共通の設定を使いたい場合も、Properties.Settings.Default を通じて簡単に共有できる。
  • ユーザーに「設定の初期化」ボタンを提供する場合は、Reset() や自前ロジックで既定値を書き戻す処理を用意する。

すでに独自 XML で設定を持っている場合

  • 現状問題なく運用できているなら、必ずしも Properties.Settings に移行する必要はない。
  • ただし、ApplicationData.Current などの WinRT API と混在させるのは避け、保存場所・仕様を一本化しておくと保守が楽。
  • XML が煩雑になり始めたら、次のバージョンで JSON など別形式への移行を検討するのも一案。

「よくある誤解」と防止メモ

最後に、今回の話題に関してありがちな誤解やハマりどころを、チェックリスト的にまとめておきます。

  • 誤解 1:App.config に値が保存されないのはバグだ
    → 仕様どおりです。
    ユーザー範囲(Scope: User)の設定は user.config に保存され、App.config はあくまで「既定値の倉庫」としてのみ使われます。
  • 誤解 2:ApplicationData.Current.LocalSettings は .NET アプリならどこでも使える
    → 実際は「パッケージ ID を持つアプリ限定」の WinRT API です。
    非パッケージの WinForms/WPF アプリでは InvalidOperationException になって当然、と覚えておきましょう。
  • 誤解 3:MSIX 化すればすべての UWP API が無条件で使える
    → ほとんどのストレージ関連 API は利用可能になりますが、機能によっては UWP 限定のもの、追加のマニフェスト設定が必要なものも存在します。
  • 誤解 4:設定の引き継ぎは自動でやってくれる
    → Properties.Settings を使う場合でも、Upgrade() を自分で呼び出さないと、旧バージョンの user.config は自動では読み込まれません。
    初回起動時にきちんと Upgrade() を呼ぶコードを書いておきましょう。
  • 誤解 5:ローミングプロファイル環境でも user.config は安全にローミングする
    → 基本的にはユーザー プロファイルとともに移動しますが、大量の設定を書き込むとプロファイルサイズが増大します。
    大きなデータは別ストレージに保存するなど、設計上の配慮が必要です。

まとめ:WinForms では「WinForms らしい」方法で設定を保存する

本記事の内容を、改めてポイントだけ箇条書きで整理します。

  • Windows.Storage.ApplicationData.Current.LocalSettings は、UWP/WinUI などのパッケージ化アプリ専用の API である。
  • 非パッケージの WinForms アプリから呼び出すと、アプリにパッケージ ID がないため InvalidOperationException が発生する。
  • WinForms では、次のいずれかを使うのが定石:
    • 解決策 A:Properties.Settings(型付き設定)でユーザー設定を管理する
    • 解決策 B:独自の JSON ファイル(settings.json など)で柔軟に管理する
    • 解決策 C:どうしても Windows.Storage を使いたい場合だけ、MSIX でパッケージ化する
  • App.config には「既定値」、実行時の変更は user.config に保存されるのが正常な挙動であり、「App.config に書き戻されない」のは仕様である。
  • バージョンアップ時に設定を引き継ぎたい場合は、初回起動時に Properties.Settings.Default.Upgrade() を呼ぶパターンが非常に有効。
  • 既存の XML ベース設定や他システムとの連携を考える場合は、JSON での管理も視野に入れると設計の自由度が上がる。

これらを踏まえてコードを整理すれば、WinForms アプリでも UWP/WinUI に負けない快適な設定管理が実現できます。まずは ApplicationData.Current.LocalSettings の呼び出しを削除し、Properties.Settings または JSON ベースの実装に置き換える ところから取り掛かるのがおすすめです。

この記事を書いた人

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

コメント

コメントする

目次