アプリケーション設定 (Application Settings)

設定の追加

ソリューション エクスプローラーでプロジェクトのプロパティを開き、そこにある[設定]ペインで追加できます。

スコープ (Scope)

設定にはスコープという項目がありますが、これは次のように使い分けます。

スコープ 用途 プログラムによる書き換え 保存場所
アプリケーション (Application) アプリケーション単位 不可 実行ファイルのフォルダ\AppName.exe.config
ユーザー (User) ユーザー単位 可能 %LOCALAPPDATA%Namespace\folder\Version\user.config
アプリケーション設定を活用するには?[2.0のみ、C#、VB] - @IT 一色政彦 (2007/02/22)

ユーザー スコープの実際の保存場所は、

System.Configuration.Configuration config = System.Configuration.ConfigurationManager.OpenExeConfiguration(
    System.Configuration.ConfigurationUserLevel.PerUserRoamingAndLocal);

string filePath = config.FilePath;

とすることで確認できます。ConfigurationManager.OpenExeConfiguration メソッド (ConfigurationUserLevel) (System.Configuration) | MSDN

参照される情報の優先順位

アプリケーション スコープの設定
  1. AppName.exe.config の既定値
  2. アセンブリ内の既定値
ユーザー スコープの設定
  1. user.config の設定値
  2. AppName.exe.config の既定値
  3. アセンブリ内の既定値

任意の型

設定デザイナの一覧に表示されない型も、コードを記述することで使用できます。簡単には設定デザイナで適当な型で作成し、Settings.settingsをテキスト エディタで開きます。そしてそこでSetting要素のType属性を、希望する型に書き替えます。たとえばList<int>としたいならば、

<Setting Name="Setting" Type="System.Collections.Generic.List&lt;int&gt;" Scope="User">

のようにします。c# - How to save a List<string> on Settings.Default? - Stack Overflow

このように型としてコレクションを指定すると、構成ファイルではXMLで記録されます。

設定値の取得

次のいずれかの書式のプロパティから取得できます。

global::Namespace.Properties.Settings.Default.Name
global::Namespace.Properties.Settings.Default["Name"]

設定の既定値の取得

SettingsPropertyクラスを介して取得できます。

string settingValue =
    (string)Settings.Default.Properties[ "SettingName" ].DefaultValue;

文字列の配列

文字列の配列はStringCollectionクラスによりXML形式で格納されるため、既定値を取得するにはこれを構文解析します。

string settingValue =
    (string)Settings.Default.Properties[ "SettingName" ].DefaultValue;

System.Xml.XmlDocument document = new System.Xml.XmlDocument();
document.LoadXml( settingValue );

foreach( System.Xml.XmlElement element in document.DocumentElement )
{
    string data = element.InnerText;
}

このStringCollectionを利用すると、これをシリアル化するときにSystem.XmlSerializers.DLLが存在しないために、FileNotFoundExceptionが投げられます。これに対処するには代わりにStringクラスを用いるか、この例外を無視します。c# - FileNotFoundException in ApplicationSettingsBase - Stack Overflow

取得した設定値の検証

バージョンアップの影響などで無効な値が取得される可能性を考慮するならば、アプリケーション設定の取得時に値の検証を行います。それにはプロジェクトのプロパティにある設定デザイナ (Settings designer) で、[コードの表示]をクリックします。C# で設定を使用する | MSDN

するとSettings.csというファイル名で、次のようなSettingクラスが表示されます。

// このクラスでは設定クラスでの特定のイベントを処理できます:
//  SettingChanging イベントは、設定値が変更される前に発生します。
//  PropertyChanged イベントは、設定値が変更された後に発生します。
//  SettingsLoaded イベントは、設定値が読み込まれた後に発生します。
//  SettingsSaving イベントは、設定値が保存される前に発生します。
internal sealed partial class Settings {

    public Settings() {
        // 設定の保存と変更のイベントハンドラを追加するには、以下の行のコメントを解除します:
        //
        // this.SettingChanging += this.SettingChangingEventHandler;
        //
        // this.SettingsSaving += this.SettingsSavingEventHandler;
        //
    }

    private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) {
        // SettingChangingEvent イベントを処理するコードをここに追加してください。
    }

    private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) {
        // SettingsSaving イベントを処理するコードをここに追加してください。
    }
}

アプリケーション設定の読み込み時に処理するには、ここでSettingsLoadedイベントのハンドラを定義します。

internal sealed partial class Settings
{
    public Settings()
    {
        this.SettingsLoaded += this.SettingsLoadedEventHandler;
    }

    private void SettingsLoadedEventHandler( object sender, System.Configuration.SettingsLoadedEventArgs e )
    {
        // 値を検証する
        if( this.settingValue < 1.0 )
        {
            // 既定値を設定する。直接キャストできないため、文字列を介して解析する
            this.settingValue = Double.Parse( (string)Settings.Default.Properties[ "SettingName" ].DefaultValue );
        }
    }
}

前バージョンの設定値の引き継ぎ

アプリケーション設定はアプリケーションのバージョンごとに異なるフォルダに保存されるため、通常ではバージョンアップ時に設定は引き継がれません。もし設定を引き継ぐようにしたいならば、ApplicationSettingsBaseのUpgrade()メソッドを呼び出します。前バージョンの設定を取得する - .NET Tips

Properties.Settings.Default.Upgrade();
ApplicationSettingsBase.Upgrade メソッド (System.Configuration) | MSDN

またバージョンアップしたとき、初回の起動時にのみ設定を引き継ぐようにするには、更新したことを示すフラグをアプリケーション設定に記録しておきます。アプリケーション設定の Upgrade について - MSDN フォーラム

設定値の設定

global::Namespace.Properties.Settings.Default.Name = 123;

保存

Properties.Settings.Default.Save()を呼ぶことで保存できます。

public override void Save()
ApplicationSettingsBase.Save メソッド (System.Configuration) | Microsoft Learn

ただし設定値に何も書き込んでおらず、保存する情報がなければ保存されません。

設定値が変更されていることを示すSettingsPropertyValue.IsDirtyは、値の型がstringやDateTime、それにint、float、realといったプリミティブ型ではない場合には、PropertyValueからアクセスされた時点で変更されたと見なされます。Remarks - SettingsPropertyValue.IsDirty Property (System.Configuration) | Microsoft Learn

値の変更の検出

イベント 発生タイミング
SettingChanging 値が変更される前。Reload()やReset()では発生しない
PropertyChanged 値が変更された後。Reload()やReset()で復元されたときにも発生する

これらのイベントはApplicationSettingsBase.Item[]を介して値が設定されるときに発生するため、同一の値が設定され、値が変更されていなくても発生します。

SettingChanging

SettingChangingEventArgs.Cancelをfalseとすることで、変更を取り消せます。

保存場所や保存方法の変更

保存場所や保存方法を変更するには、SettingsProviderを継承したクラスを作成し、それをSettingsクラスにSettingsProviderAttributeで指定します。

独自に定義したクラスは、プロパティ ウィンドウを表示した状態で設定デザイナで設定項目を選択し、[Provider]でクラス名を指定することでも適用できます。しかしこの方法では設定項目ごとに独自定義のクラスが呼ばれるため、Settingsクラスで指定するようにします。

SettingsProviderクラスの拡張

class CustomSettingsProvider : SettingsProvider
{
    private struct Setting
    {
        public string name;
        public string value;
        public SettingsSerializeAs serializeAs;
    }

    private readonly string userConfigPath;
    private Dictionary<string, Setting> settingsDictionary = null;

    public override string ApplicationName
    {
        get
        {
            System.Reflection.Assembly assembly = typeof(Program).Assembly;
            System.Reflection.AssemblyName assemblyName = assembly.GetName();
            return assemblyName.Name;
        }
        set
        {
        }
    }


    public CustomSettingsProvider()
    {
        this.userConfigPath = @"C:\user.config";
    }

    public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
    {
        base.Initialize(ApplicationName, config);
    }

    public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
    {
        if (this.settingsDictionary == null)
        {
            this.settingsDictionary = LoadSettingValues(this.userConfigPath);
        }

        SettingsPropertyValueCollection propertyValues = new SettingsPropertyValueCollection();
        foreach (SettingsProperty property in collection)
        {
            SettingsPropertyValue propertyValue = new SettingsPropertyValue(property);

            object value;
            if (this.settingsDictionary.ContainsKey(property.Name))
            {
                value = this.settingsDictionary[property.Name].value;
            }
            else
            {
                value = property.DefaultValue;
            }
            propertyValue.SerializedValue = value;

            if (value != null)
            {
                string text = value.ToString();
                if (property.SerializeAs == SettingsSerializeAs.String)
                {
                    System.ComponentModel.TypeConverter typeConverter = System.ComponentModel.TypeDescriptor.GetConverter(property.PropertyType);
                    propertyValue.PropertyValue = typeConverter.ConvertFromString(text);
                }
                else if (property.SerializeAs == SettingsSerializeAs.Xml)
                {
                    using (System.IO.StringReader stringReader = new System.IO.StringReader(text))
                    using (System.Xml.XmlReader xmlReader = System.Xml.XmlReader.Create(stringReader))
                    {
                        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(property.PropertyType);
                        propertyValue.PropertyValue = serializer.Deserialize(xmlReader);
                    }
                }
                else
                {
                    throw new NotImplementedException();
                }
            }

            propertyValue.IsDirty = false;
            propertyValues.Add(propertyValue);
        }

        return propertyValues;
    }

    public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
    {
        foreach (SettingsPropertyValue propertyValue in collection)
        {
            if (!propertyValue.IsDirty) continue;
            if (!propertyValue.Property.Attributes.Contains(typeof(UserScopedSettingAttribute))) continue;

            Setting setting = new Setting()
            {
                name = propertyValue.Name,
                value = propertyValue.SerializedValue.ToString(),
                serializeAs = propertyValue.Property.SerializeAs
            };

            this.settingsDictionary[propertyValue.Name] = setting;
        }

        SaveSettingValues(this.userConfigPath, this.settingsDictionary);
    }


    private static Dictionary<string, Setting> LoadSettingValues(string path)
    {
        Dictionary<string, Setting> dictionary = new Dictionary<string, Setting>();
        if (!System.IO.File.Exists(path)) return dictionary;

        using (System.Xml.XmlReader reader = System.Xml.XmlReader.Create(path))
        {
            while (reader.ReadToFollowing("setting"))
            {
                string name = reader.GetAttribute("name");
                string serializeAs = reader.GetAttribute("serializeAs");
                string value = reader.ReadElementContentAsString();

                if (String.IsNullOrEmpty(name)) continue;

                Setting setting = new Setting()
                {
                    name = name,
                    value = value,
                    serializeAs = SettingsSerializeAs.String
                };

                SettingsSerializeAs settingsSerializeAs;
                if (Enum.TryParse(serializeAs, out settingsSerializeAs))
                {
                    setting.serializeAs = settingsSerializeAs;
                }

                if (!dictionary.ContainsKey(setting.name))
                {
                    dictionary.Add(setting.name, setting);
                }
            }
        }
        return dictionary;
    }

    private static void SaveSettingValues(string path, Dictionary<string, Setting> dictionary)
    {
        System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings();
        settings.Indent = true;

        using (System.Xml.XmlWriter writer = System.Xml.XmlWriter.Create(path, settings))
        {
            writer.WriteStartElement("configuration");
            writer.WriteStartElement("userSettings");
            writer.WriteStartElement(typeof(Properties.Settings).FullName);

            foreach (Setting setting in dictionary.Values)
            {
                writer.WriteStartElement("setting");
                writer.WriteAttributeString("name", setting.name);
                writer.WriteAttributeString("serializeAs", setting.serializeAs.ToString());
                writer.WriteString(setting.value ?? String.Empty);
                writer.WriteEndElement();
            }

            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
    }
}
SettingsProvider Class (System.Configuration) | Microsoft Learn

Settingsクラスへの適用

[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "*.0.0.0")]
[global::System.Configuration.SettingsProviderAttribute(typeof(CustomSettingsProvider))]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {

SettingsProviderAttribute Class (System.Configuration) | Microsoft Learn

関連ファイル

ファイル   作成時機
プロジェクトのフォルダ\App.config 構成ファイル プロジェクトの作成時
プロジェクトのフォルダ\Properties\Settings.settings    
実行ファイルのフォルダ\AppName.exe.config   プロジェクトのビルド時
%LOCALAPPDATA%Namespace\folder\Version\user.config   Properties.Settings.Default.Save() 呼び出し時

設定デザイナでの編集はApp.configに反映され、その内容はビルド時にAppName.exe.configにコピーされます。

構成ファイル (Configuration Files)

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="WindowsFormsApplication1.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
        </sectionGroup>
        <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="WindowsFormsApplication1.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
        </sectionGroup>
    </configSections>

    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
    </startup>

    <userSettings>
        <WindowsFormsApplication1.Properties.Settings>
            <setting name="location" serializeAs="String">
                <value>10, 20</value>
            </setting>
            <setting name="text" serializeAs="String">
                <value>Form1</value>
            </setting>
        </WindowsFormsApplication1.Properties.Settings>
    </userSettings>

    <applicationSettings>
        <WindowsFormsApplication1.Properties.Settings>
            <setting name="sample" serializeAs="String">
                <value>123</value>
            </setting>
        </WindowsFormsApplication1.Properties.Settings>
    </applicationSettings>
</configuration>

スキーマ

  内容
configuration すべての構成ファイルのルート要素
configSections 構成セクションの名前空間の定義 (sectionGroup) と構成セクションの宣言 (section)
構成セクション スキーマ | Microsoft Learn
  内容
startup アプリケーションを実行するのに必要な、共通言語ランタイムのバージョンを示す
supportedRuntime アプリケーションがサポートする共通言語ランタイムのバージョン。オプションで.NET Frameworkのバージョン
  • version属性 … 共通言語ランタイムのバージョン。.NET Framework 4.0~4.8ならば、"v4.0"とする
  • sku属性 … 最小在庫管理単位 (stock-keeping unit / SKU)。.NET Framework 4.8ならば、".NETFramework,Version=v4.8"とする
スタートアップ設定スキーマ | Microsoft Learn
  内容
userSettings 現在のユーザー固有のsetting要素
applicationSettings アプリケーション康祐のsetting要素
アプリケーション設定のスキーマ | Microsoft Learn

ConfigurationErrorsException

この構成ファイルに誤りがあると、「認識されない構成セクション userSettings です。 (C:\\***\\user.config line 3)」としてConfigurationErrorsExceptionが投げられることがあります。この場合は、このファイルを一度削除してみます。

Microsoft Learnから検索