RCIE-ジャンクのコード屋

主に自分のためにコーディングのTIPSを蓄積しています。

(C#)アプリケーションの設定を簡単に読み書きするクラス

作ったもの

 アプリケーションの設定を簡単に読み書きするクラスを作りました。ウィンドウのサイズ、最後に開いたファイルなどを保存するのに使えます。
 保存先は、AppData\Local\[会社名]\[アプリケーション名]です。
rcie.hatenablog.com

前提

using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;

コード

/// <summary>
/// 文字列でアプリケーションの設定を読み書きするクラス。
/// </summary>
static class Config {
	private static Dictionary<string, string> dic;
	private static string path;
	static Config() {
		path = LocalAppData;
		Reload();
	}
	/// <summary>
	/// キーに応じた値を取得する。
	/// 存在しない場合は""を取得する。nullではない。
	/// </summary>
	/// <param name="key">キー</param>
	/// <returns></returns>
	public static string Get(string key) {
		string value;
		return dic.TryGetValue(key, out value) ? (value ?? "") : "";
	}
	/// <summary>
	/// キーに応じた値を整数値として取得する。
	/// 存在しない場合はデフォルト値を取得する。
	/// </summary>
	/// <param name="key">キー</param>
	/// <param name="defaultValue">キーが存在しない場合、整数値ではない場合のデフォルト値</param>
	/// <returns>整数値</returns>
	public static int GetInt(string key, int defaultValue = 0) {
		if (dic.ContainsKey(key)) {
			int n;
			return Int32.TryParse(dic[key], out n) ? n : defaultValue;
		}
		return defaultValue;
	}
	/// <summary>
	/// キーに応じた値を実数値として取得する。
	/// 存在しない場合はデフォルト値を取得する。
	/// </summary>
	/// <param name="key">キー</param>
	/// <param name="defaultValue">キーが存在しない場合、整数値ではない場合のデフォルト値</param>
	/// <returns>実数値</returns>
	public static double GetDouble(string key, double defaultValue = double.NaN) {
		if (dic.ContainsKey(key)) {
			double n;
			return Double.TryParse(dic[key], out n) ? n : defaultValue;
		}
		return defaultValue;
	}
	/// <summary>
	/// キーに対して値を設定する。
	/// </summary>
	/// <param name="key">キー</param>
	/// <param name="value"></param>
	public static void Set(string key, string value) {
		dic[key] = value;
	}
	/// <summary>
	/// AppData\Localの中にある設定保存用のディレクトリを取得します。
	/// [ユーザー名]\AppData\Local\[会社名]\[アプリケーション名]
	/// 会社名が設定されていない場合は "Default" になります。
	/// </summary>
	static private string LocalAppData {
		get {
			Func<string, bool> nameHasForbiddenChar = (str) => {
				foreach (var each in "\\/:*?\"<>|") {
					if (str.Contains(each + "")) {
						return true;
					}
				}
				return false;
			};
			Assembly asm = Assembly.GetExecutingAssembly();
			string asmName = asm.GetName().Name;
			Attribute customAttr = Attribute.GetCustomAttribute(
				asm, typeof(AssemblyCompanyAttribute));
			string company = (customAttr as AssemblyCompanyAttribute).Company;
			if (company.Length == 0 || nameHasForbiddenChar(company)) {
				company = "Default";
			}
			string path = Environment.GetFolderPath(
				Environment.SpecialFolder.LocalApplicationData);
			return path + "\\" + company + "\\" + asmName;
		}
	}
	/// <summary>
	/// 現在の状態を破棄して、設定ファイルから再度読み込む。
	/// </summary>
	public static void Reload() {
		Directory.CreateDirectory(path);
		string configtxt = path + "\\config.txt";
		try {
			string s = File.ReadAllText(configtxt, Encoding.UTF8);
			dic = StringからDictionaryへ(s);
		}
		catch (Exception) {
			dic = new Dictionary<string, string>();
		}
		return;
	}
	/// <summary>
	/// 現在の状態を、設定ファイルに書き込む。
	/// </summary>
	public static void Save() {
		Directory.CreateDirectory(path);
		string configtxt = path + "\\config.txt";
		try {
			string s = DictionaryからStringへ(dic);
			File.WriteAllText(configtxt, s, Encoding.UTF8);
		}
		catch (Exception ex) {
			throw (ex);
		}
		return;
	}
	private enum Status {
		KEY, KEY_ESC, VALUE, VALUE_ESC, CR, END
	}
	private static Dictionary<string, string> StringからDictionaryへ(string s) {
		var sbKey = new StringBuilder();
		var sbVal = new StringBuilder();
		var result = new Dictionary<string, string>();
		Status status = Status.KEY;
		Action<char> 行末処理 = (c) => {
			if (c == '\n' && sbKey.Length > 0 && sbVal.Length > 0) {
				result[sbKey.ToString()] = sbVal.ToString();
			}
			sbKey.Clear();
			sbVal.Clear();
			status = Status.KEY;
		};
		foreach (char c in s) {
			switch (status) {
				case Status.KEY:
					if (c == '\\') {
						status = Status.KEY_ESC;
					} else if (c == '=') {
						status = Status.VALUE;
					} else if (c == '\r') {
						status = Status.CR;
					} else {
						sbKey.Append(c);
					}
					continue;
				case Status.KEY_ESC:
					if (c == 'r') {
						sbKey.Append('\r');
					} else if (c == 'n') {
						sbKey.Append('\n');
					} else if (c == 't') {
						sbKey.Append('\t');
					} else {
						sbKey.Append(c);
					}
					status = Status.KEY;
					continue;
				case Status.VALUE:
					if (c == '\\') {
						status = Status.VALUE_ESC;
					} else if (c == '\r') {
						status = Status.CR;
					} else {
						sbVal.Append(c);
					}
					continue;
					case Status.VALUE_ESC:
					if (c == 'r') {
						sbVal.Append('\r');
					} else if (c == 'n') {
						sbVal.Append('\n');
					} else if (c == 't') {
						sbVal.Append('\t');
					} else {
						sbVal.Append(c);
					}
					status = Status.VALUE;
					continue;
				case Status.CR:
					行末処理(c);
					continue;
			}
		}
		行末処理('\n');
		return result;
	}
	private static string DictionaryからStringへ(Dictionary<string, string> arg) {
		Func<string, string> Unescape = (s) => {
			if(s == null) {
				return "";
			}
			var sb = new StringBuilder();
			foreach (var c in s) {
				if (c == '=' || c == '\\') {
					sb.Append('\\').Append(c);
				} else if (c == '\r') {
					sb.Append("\\r");
				} else if (c == '\n') {
					sb.Append("\\n");
				} else if (c == '\t') {
					sb.Append("\\t");
				} else {
					sb.Append(c);
				}
			}
			return sb.ToString();
		};
		var result = new StringBuilder();
		foreach (var pair in arg) {
			var key = Unescape(pair.Key);
			if(key.Length == 0) {
				break;
			}
			var value = Unescape(pair.Value);
			if(value.Length == 0) {
				break;
			}
			if (result.Length > 0) {
				result.Append("\r\n");
			}
			result.Append(key).Append('=').Append(value);
		}
		return result.ToString();
	}
}

使い方

string s = Config.Get("name"); // "name" というキーに対応する文字列の取得
int n = Config.GetInt("age", -1); // "age" というキーに対応する整数値の取得。キーがなければ -1
double d = Config.GetDouble("height"); // "height" というキーに対応する実数値の取得。キーがなければ NaN
Config.Set("name", "太郎"); // "name" というキーに "太郎" を設定する
Config.Save(); // C:\Users\[ユーザー名]\AppData\Local\[会社名]\[アプリケーション名]\config.txt に保存する

解説

 DictionaryXMLシリアライズするのではなく、「key=value」形式で簡易的に保存します。エンコード形式は UTF-8 です。