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