C# 응용 프로그램을 만들다보면 설정 파일이 필요한 경우가 있다.

이때 ini file을 사용하면 간편하다. 물론 xml로 된 config file도 있지만 ini file이 직관적이고 수정하기 편하다.

구글링을 해보면 C#에서 ini파일을 다루는 여러 내용들이 있지만, 효율적으로 ini file을 관리하고 수정이 용이하게 설명된 내용이 없어 직접 만든 내용을 공유하려 한다.

그렇다고 내가 만든게 짱짱맨 이런 의미는 아니다.


ini file의 구성은

크게 section과 key로 나뉜다.

예를 들면

[Database]

Address="localhost"

Id="root"

[User]

Name="홍길동"

Age="29"


이런 ini file이 있을때 [Database]와 [User]가 section이 되고 Address, id, Name, Age가 key가 된다.


아래 소스를 보면서 하나씩 짚어가보자

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
/// System.ComponentModel.DataAnnotations 는 참조에서 어셈블리를 추가해야 한다.
using System.ComponentModel.DataAnnotations;
using System.Runtime.InteropServices;
using System.Text;

namespace ConfigManager
{
	/// <summary>
	/// ini file의 정보를 담을 그릇(클래스)
	/// <typeparam name="K1"></typeparam>
	/// <typeparam name="K2"></typeparam>
	/// <typeparam name="V"></typeparam>
	[Serializable]
	public class MultiKeyDictionary<k1, k2, v> : Dictionary<k1, dictionary<k2, v>>
	{
		public V this[K1 key1, K2 key2]
		{
			get
			{
				if (!ContainsKey(key1) || !this[key1].ContainsKey(key2))
				{
					return default(V);
					//throw new ArgumentOutOfRangeException();
				}

				return base[key1][key2];
			}
			set
			{
				if (!ContainsKey(key1))
					this[key1] = new Dictionary<k2, v>();
				this[key1][key2] = value;
			}
		}

		public void Add(K1 key1, K2 key2, V value)
		{
			if (!ContainsKey(key1))
				this[key1] = new Dictionary<k2, v>();
			this[key1][key2] = value;
		}

		public bool ContainsKey(K1 key1, K2 key2)
		{
			return base.ContainsKey(key1) && this[key1].ContainsKey(key2);
		}

		public new IEnumerable<v> Values
		{
			get
			{
				return from baseDict in base.Values
					   from baseKey in baseDict.Keys
					   select baseDict[baseKey];
			}
		}
	}

	/// <summary>
	/// Section 구조
	/// 추가할 Section이 있을 경우 enum에 추가하고 section name만 정의해주면 된다.
	/// </summary>
	public enum SectionNames
	{
		/// [Display(Name = "Database")] 에서 Name에 들어가는 문자열이 ini file에 실질적으로 쓰여지는 Section의 이름이다.

		/// <summary>
		///  Section Name "Database"
		/// </summary>
		[Display(Name = "Database")]
		//해당 이름과 아래 선언된 enum의 이름이 일치해야한다.
		Database_Section,

		/// <summary>
		/// Section Name "User"
		/// </summary>
		[Display(Name = "User")]
		User_Section,
	}

	/// <summary>
	/// Database Section의 Key 나열
	/// 추가할 key가 있을 경우 enum에 추가하고 디폴트 값만 정의해주면 된다.
	/// </summary>
	public enum Database_Section
	{
		/// [Display(Description = "localhost")]에서 Description에 들어가는 문자열은 ini에 key가 없는 경우 디폴트로 들어가는 값이다.

		/// <summary>
		/// Default Value : localhost
		/// </summary>
		[Display(Description = "localhost")]
		// Address 그대로 ini file의 key값으로 쓰여진다. 
		Address,

		/// <summary>
		/// Default Value : root
		/// </summary>
		[Display(Description = "root")]
		// Id 그대로 ini file의 key값으로 쓰여진다.
		Id,
	}

	/// <summary>
	/// User Section의 Key 나열
	/// </summary>
	public enum User_Section
	{
		/// <summary>
		/// Default Value : 홍길동
		/// </summary>
		[Display(Description = "홍길동")]
		Name,

		/// <summary>
		/// Default Value : 29
		/// </summary>
		[Display(Description = "29")]
		Age,
	}

	public class ConfigData
	{
		//ini file의 경로 설정
		//원하는 경로와 이름으로 바꿔주면 된다.
		private string inifilepath = @"C:\Config.ini";

		//ini file의 정보를 담을 변수 선언
		private MultiKeyDictionary<sectionnames, string, object> _ConfigData = null;

		//ini file을 읽을때 사용하는 함수
		[DllImport("kernel32.dll")]
		private static extern int GetPrivateProfileString(
			String section, String key, String def, StringBuilder retVal, int Size, String filePath);

		//ini file을 쓸때 사용하는 함수
		[DllImport("kernel32.dll")]
		private static extern long WritePrivateProfileString(
			String Section, String key, String val, String filePath);

		public ConfigData()
		{
			_ConfigData = new MultiKeyDictionary<sectionnames, string, object>();
		}

		/// <summary>
		/// ConfigData Class내에서 ini file을 쓰기 위한 함수
		/// </summary>
		/// <param name="Section">
		/// <param name="Key">
		/// <param name="Value">
		private void IniWriteValue(String Section, String Key, String Value)
		{
			WritePrivateProfileString(Section, Key, Value, inifilepath);
		}

		/// <summary>
		/// ConfigData Class내에서 ini file을 읽기 위한 함수
		/// </summary>
		/// <param name="Section">
		/// <param name="Key">
		/// <returns></returns>
		private String IniReadValue(String Section, String Key)
		{
			StringBuilder temp = new StringBuilder(255);
			int i = GetPrivateProfileString(Section, Key, "", temp, 255, inifilepath);

			return temp.ToString();
		}

		/// <summary>
		/// 외부에서 ConfigData Class 객체를 이용해 Ini file의 값을 셋팅하기 위한 함수
		/// </summary>
		/// <typeparam name="KeyEnum"></typeparam>
		/// <param name="key">
		/// <param name="value">
		public void SetConfigData<keyenum>(KeyEnum key, string value)
		{
			DisplayAttribute keyAttr = typeof(KeyEnum).GetMember(key.ToString())
											.First()
											.GetCustomAttribute<displayattribute>();

			SectionNames sectionName = (SectionNames)Enum.Parse(typeof(SectionNames), typeof(KeyEnum).Name);

			_ConfigData[sectionName][key.ToString()] = value;
		}

		/// <summary>
		/// 외부에서 ConfigData Class 객체를 이용해 Ini file의 값을 가져오기 위한 함수
		/// </summary>
		/// <typeparam name="KeyEnum"></typeparam>
		/// <param name="key">
		/// <returns></returns>
		public string getConfigData<keyenum>(KeyEnum key)
		{
			DisplayAttribute keyAttr = typeof(KeyEnum).GetMember(key.ToString())
											.First()
											.GetCustomAttribute<displayattribute>();

			SectionNames sectionName = (SectionNames)Enum.Parse(typeof(SectionNames), typeof(KeyEnum).Name);

			return _ConfigData[sectionName][key.ToString()].ToString();
		}

		/// <summary>
		/// Ini file을 저장하기 위한 함수
		/// </summary>
		public void SaveConfigData()
		{
			for (int i = 0; i < _ConfigData.Count; i++)
			{
				for (int j = 0; j < _ConfigData.ElementAt(i).Value.Count; j++)
				{
					string sectionname = _ConfigData.ElementAt(i).Key.ToString();


					DisplayAttribute keyAttr = typeof(SectionNames).GetMember(sectionname)
														.First()
														.GetCustomAttribute<displayattribute>();

					string section = keyAttr.Name;
					string key = _ConfigData.ElementAt(i).Value.ElementAt(j).Key.ToString();
					string value = _ConfigData.ElementAt(i).Value.ElementAt(j).Value.ToString();
					IniWriteValue(
						section,    //section
						key,        //key
						value       //value
						);
				}
			}
		}

		/// <summary>
		/// Ini file을 읽기 위한 함수
		/// </summary>
		public void ReadConfigData()
		{
			foreach (string strSectionName in Enum.GetNames(typeof(SectionNames)))
			{
				SectionNames sectionName = (SectionNames)Enum.Parse(typeof(SectionNames), strSectionName);

				DisplayAttribute sectionAttr = typeof(SectionNames).GetMember(strSectionName)
													  .First()
													  .GetCustomAttribute<displayattribute>();

				if (!_ConfigData.ContainsKey(sectionName))
					_ConfigData.Add(sectionName, new Dictionary<string, object="">());

				Type keyType = Type.GetType(string.Format("ConfigManager.{0}", sectionName));

				foreach (string strKeyName in Enum.GetNames(keyType))
				{
					DisplayAttribute keyAttr = keyType.GetMember(strKeyName)
													  .First()
													  .GetCustomAttribute<displayattribute>();

					string value = IniReadValue(sectionAttr.Name, strKeyName);

					if (!_ConfigData[sectionName].ContainsKey(strKeyName)) // 키가 없는 경우 추가
					{
						if (!string.IsNullOrEmpty(value)) // ini File에 값이 있다면 ini File 값 입력
							_ConfigData[sectionName].Add(strKeyName, value);
						else // ini File에 값이 없다면 Default 값 입력
							_ConfigData[sectionName].Add(strKeyName, keyAttr.Description);
					}
					else // 키가 있는 경우 값 입력
					{
						if (!string.IsNullOrEmpty(value)) // ini File에 값이 있다면 ini File 값 입력
							_ConfigData[sectionName][strKeyName] = value;
						else // ini File에 값이 없다면 Default 값 입력
							_ConfigData[sectionName][strKeyName] = keyAttr.Description;
					}
				}
			}
		}
	}
}

//이렇게 class를 만들었다. 실질적으로 사용하는 방법은 이렇다
using ConfigManager;

ConfigData _config = new ConfigData();
// ini file read
_config.ReadConfigData();

// 각 key에서 값 가져오기
string address = _config.getConfigData<database_section>(Database_Section.Address);
string id = _config.getConfigData<database_section>(Database_Section.Id);
string name = _config.getConfigData<user_section>(User_Section.Age);
string age = _config.getConfigData<user_section>(User_Section.Name);

// 각 key에 값 할당
// 값을 셋팅만 하지 실질적으로 ini file에 쓰기 위해선 save를 해줘야한다.
string address_change = "127.0.0.1";
string id_change = "server";
string name_change = "시나공공";
string age_change = "28"; // 늙었다...

_config.SetConfigData<database_section>(Database_Section.Address, address_change);
_config.SetConfigData<database_section>(Database_Section.Id, id_change);
_config.SetConfigData<User_Section>(User_Section.Name, name_change);
_config.SetConfigData<User_Section>(User_Section.Age, age_change);

// ini file save
_config.SaveConfigData();


구조적으로, 프로그래밍 적으로 좋은 방법인지 아닌지는 객관적으로 잘 모르겠으나, 내 나름대로 쓰기에 나쁘지 않다고 생각하여 공유해본다.

조언 및 충고 해주시면 너무너무 감사하겠습니다.

'Programming > C#' 카테고리의 다른 글

[C#] 네이버 자동 로그인 / 카페 글쓰기 API  (9) 2019.11.01
[C#]ini file 사용법 & 소스 공유  (3) 2017.01.03
[C#]Log4Net 사용법  (2) 2017.01.03
[C#]Snappy 사용법  (5) 2016.12.30
[C#]DataTable Sorting 간단 사용법  (0) 2016.12.30
  1. 2020.01.07 10:26

    비밀댓글입니다

    • https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/generics/generic-methods

      제너릭메소드라고 합니다. 구글에 제너릭 메소드라고 검색하시면 많이 나와요~

  2. m 2020.01.07 13:21

    좋은자료 감사합니다

    Dictionary도 그렇고 enum도 생소한게 많네요.

+ Recent posts