using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace UVC.Extention { /// /// 키의 순서가 보장되는 스레드 안전한 제네릭 컬렉션을 나타냅니다. /// 항목이 추가된 순서대로 유지되며, 인덱스를 통해 접근할 수 있습니다. /// /// 사전의 키 형식입니다. /// 사전의 값 형식입니다. /// /// 다음은 OrderedDictionary의 사용 예제입니다. /// /// // OrderedDictionary 인스턴스 생성 /// var cityPopulation = new OrderedDictionary(); /// /// // 데이터 추가 (순서대로 추가됨) /// cityPopulation.Add("서울", 9700000); /// cityPopulation.Add("부산", 3400000); /// cityPopulation.Add("인천", 3000000); /// /// // 키를 이용한 값 접근 /// Console.WriteLine($"서울 인구: {cityPopulation["서울"]}"); // 출력: 서울 인구: 9700000 /// /// // 인덱스를 이용한 값 접근 (순서 보장) /// Console.WriteLine($"첫 번째 추가된 도시의 인구: {cityPopulation.GetValueAt(0)}"); // 출력: 첫 번째 추가된 도시의 인구: 9700000 /// /// // 특정 위치에 데이터 삽입 /// cityPopulation.Insert(1, "대구", 2400000); // "부산" 앞에 "대구" 삽입 /// /// // 추가된 순서대로 반복 /// Console.WriteLine("\n현재 도시 목록 (순서 보장):"); /// foreach(var city in cityPopulation) /// { /// Console.WriteLine($"- {city.Key}: {city.Value}"); /// } /// // 출력: /// // - 서울: 9700000 /// // - 대구: 2400000 /// // - 부산: 3400000 /// // - 인천: 3000000 /// /// // 항목 삭제 /// cityPopulation.Remove("부산"); /// /// Console.WriteLine("\n'부산' 삭제 후 도시 목록:"); /// foreach(var city in cityPopulation) /// { /// Console.WriteLine($"- {city.Key}: {city.Value}"); /// } /// /// public partial class OrderedDictionary : IDictionary { private readonly object _lock = new object(); private readonly Dictionary _dict; private readonly List _keys; #region Constructors /// /// 비어 있고 기본 초기 용량을 가지며 키 형식에 대한 기본 같음 비교자를 사용하는 클래스의 새 인스턴스를 초기화합니다. /// public OrderedDictionary() { _dict = new Dictionary(); _keys = new List(); } /// /// 비어 있고 기본 초기 용량을 가지며 지정된 를 사용하는 클래스의 새 인스턴스를 초기화합니다. /// /// 키를 비교할 때 사용할 구현 또는 키 형식에 대한 기본 를 사용하려면 null입니다. public OrderedDictionary(IEqualityComparer comparer) { _dict = new Dictionary(comparer); _keys = new List(); } /// /// 비어 있고 지정된 초기 용량을 가지며 키 형식에 대한 기본 같음 비교자를 사용하는 클래스의 새 인스턴스를 초기화합니다. /// /// 가 초기에 포함할 수 있는 요소의 수입니다. public OrderedDictionary(int capacity) { _dict = new Dictionary(capacity); _keys = new List(capacity); } /// /// 비어 있고 지정된 초기 용량을 가지며 지정된 를 사용하는 클래스의 새 인스턴스를 초기화합니다. /// /// 가 초기에 포함할 수 있는 요소의 수입니다. /// 키를 비교할 때 사용할 구현 또는 키 형식에 대한 기본 를 사용하려면 null입니다. public OrderedDictionary(int capacity, IEqualityComparer comparer) { _dict = new Dictionary(capacity, comparer); _keys = new List(capacity); } /// /// 지정된 에서 복사한 요소를 포함하고 키 형식에 대한 기본 같음 비교자를 사용하는 클래스의 새 인스턴스를 초기화합니다. /// /// 요소가 새 에 복사되는 입니다. public OrderedDictionary(IDictionary dictionary) { _dict = new Dictionary(dictionary); _keys = new List(dictionary.Keys); } /// /// 지정된 에서 복사한 요소를 포함하고 지정된 를 사용하는 클래스의 새 인스턴스를 초기화합니다. /// /// 요소가 새 에 복사되는 입니다. /// 키를 비교할 때 사용할 구현 또는 키 형식에 대한 기본 를 사용하려면 null입니다. public OrderedDictionary(IDictionary dictionary, IEqualityComparer comparer) { _dict = new Dictionary(dictionary, comparer); _keys = new List(dictionary.Keys); } #endregion #region IDictionary Members /// /// 사전에 지정된 키와 값을 추가합니다. /// /// 추가할 요소의 키입니다. /// 추가할 요소의 값입니다. public void Add(TKey key, TValue value) { lock (_lock) { if (_dict.ContainsKey(key)) throw new ArgumentException("이미 존재하는 키입니다."); _keys.Add(key); _dict.Add(key, value); } } /// /// 사전에 지정된 키가 포함되어 있는지 확인합니다. /// /// 찾을 키입니다. /// 사전에 지정된 키를 가진 요소가 있으면 true이고, 그렇지 않으면 false입니다. public bool ContainsKey(TKey key) { lock (_lock) { return _dict.ContainsKey(key); } } /// /// 사전의 키를 순서대로 포함하는 컬렉션을 가져옵니다. /// public ICollection Keys { get { lock (_lock) { return _keys.AsReadOnly(); } } } /// /// 사전에서 지정된 키를 가진 요소를 제거합니다. /// 참고: 이 작업은 내부 리스트에서 키를 찾아 제거하므로 O(n)의 시간 복잡도를 가질 수 있습니다. /// /// 제거할 요소의 키입니다. /// 요소를 성공적으로 찾아 제거했으면 true이고, 그렇지 않으면 false입니다. public bool Remove(TKey key) { lock (_lock) { if (_dict.Remove(key)) { _keys.Remove(key); return true; } return false; } } /// /// 지정된 키와 연결된 값을 가져옵니다. /// /// 가져올 값의 키입니다. /// 이 메서드가 반환될 때, 키가 있으면 해당 키와 연결된 값을 포함하고, 그렇지 않으면 value 매개 변수의 형식에 대한 기본값을 포함합니다. /// 사전에 지정된 키를 가진 요소가 있으면 true이고, 그렇지 않으면 false입니다. public bool TryGetValue(TKey key, out TValue value) { lock (_lock) { return _dict.TryGetValue(key, out value); } } /// /// 사전의 값을 순서대로 포함하는 컬렉션을 가져옵니다. /// public ICollection Values { get { lock (_lock) { // ToList()를 호출하여 즉시 평가된 읽기 전용 컬렉션을 반환합니다. // 이는 스레드 안정성을 위해 중요합니다. return _keys.Select(key => _dict[key]).ToList().AsReadOnly(); } } } /// /// 지정된 키와 연결된 값을 가져오거나 설정합니다. /// /// 가져오거나 설정할 요소의 키입니다. /// 지정된 키와 연결된 값입니다. public TValue this[TKey key] { get { lock (_lock) { return _dict[key]; } } set { lock (_lock) { if (!_dict.ContainsKey(key)) { _keys.Add(key); } _dict[key] = value; } } } #endregion #region ICollection> Members void ICollection>.Add(KeyValuePair item) { Add(item.Key, item.Value); } /// /// 사전에서 모든 키와 값을 제거합니다. /// public void Clear() { lock (_lock) { _dict.Clear(); _keys.Clear(); } } bool ICollection>.Contains(KeyValuePair item) { lock (_lock) { return ((ICollection>)_dict).Contains(item); } } public void CopyTo(KeyValuePair[] array, int arrayIndex) { lock (_lock) { if (array == null) throw new ArgumentNullException(nameof(array)); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); if (array.Length - arrayIndex < Count) throw new ArgumentException("대상 배열이 충분히 크지 않습니다."); foreach (var key in _keys) { array[arrayIndex++] = new KeyValuePair(key, _dict[key]); } } } /// /// 사전에 포함된 키/값 쌍의 수를 가져옵니다. /// public int Count { get { lock (_lock) { return _dict.Count; } } } /// /// 사전이 읽기 전용인지 여부를 나타내는 값을 가져옵니다. /// public bool IsReadOnly => false; bool ICollection>.Remove(KeyValuePair item) { lock (_lock) { if (((ICollection>)_dict).Contains(item)) { return Remove(item.Key); } return false; } } #endregion #region IEnumerable> Members /// /// 사전을 반복하는 열거자를 반환합니다. /// 주의: 열거가 진행되는 동안 컬렉션이 다른 스레드에서 수정되면 예외가 발생할 수 있습니다. 스레드 안전한 열거를 위해서는 컬렉션을 복사하거나 lock을 사용해야 합니다. /// /// 사전을 반복하는 데 사용할 수 있는 입니다. public IEnumerator> GetEnumerator() { // GetEnumerator는 스레드 안정성을 위해 컬렉션의 복사본을 만들어 순회하는 것이 일반적입니다. List> listCopy; lock (_lock) { listCopy = new List>(Count); foreach (var key in _keys) { listCopy.Add(new KeyValuePair(key, _dict[key])); } } return listCopy.GetEnumerator(); } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region OrderedDictionary Specific Members /// /// 사전의 지정된 인덱스에 키/값 쌍을 삽입합니다. /// 참고: 이 작업은 내부 리스트에 요소를 삽입하므로 O(n)의 시간 복잡도를 가질 수 있습니다. /// /// 키/값 쌍을 삽입할 인덱스(0부터 시작)입니다. /// 삽입할 요소의 키입니다. /// 삽입할 요소의 값입니다. public void Insert(int index, TKey key, TValue value) { lock (_lock) { if (_dict.ContainsKey(key)) throw new ArgumentException("이미 존재하는 키입니다."); _keys.Insert(index, key); _dict.Add(key, value); } } /// /// 사전의 지정된 인덱스에 있는 요소를 제거합니다. /// 참고: 이 작업은 내부 리스트에서 요소를 제거하므로 O(n)의 시간 복잡도를 가질 수 있습니다. /// /// 제거할 요소의 인덱스(0부터 시작)입니다. public void RemoveAt(int index) { lock (_lock) { if (index < 0 || index >= _keys.Count) throw new ArgumentOutOfRangeException(nameof(index)); TKey key = _keys[index]; _keys.RemoveAt(index); _dict.Remove(key); } } /// /// 지정된 인덱스의 값을 가져옵니다. /// /// 가져올 값의 인덱스 /// 지정된 인덱스에 있는 항목의 값 public TValue GetValueAt(int index) { lock (_lock) { return _dict[_keys[index]]; } } #endregion } }