Skip to main content

[C#] 컬렉션




컬렉션이란? #

C#의 자료구조
Image Source

  • C#에서 지원하는 자료구조 클래스이다. C++의 컨테이너와 비슷하다.
  • 컬렉션은 제네릭(Generic)논제네릭(Non-Generic) 으로 나뉜다.
    • 논제네릭은 object 형식을 사용해서 데이터를 관리하기 때문에 박싱과 언박싱이 잦아 성능 문제가 있어서 제네릭 컬렉션을 사용한다.
특징 Generic Non-Generic
인덱스 List<T> Array, ArrayList
키 - 값 쌍 Dictionary<TKey,TValue> Hashtable
FIFO Queue<T> Queue
LIFO Stack<T> Stack
순서대로 엑세스 LinkedList<T>
정렬된 컬렉션 SortedList<TKey,TValue> SortedList
수학함수용 집합 HashSet<T>



불변성과 가변성 #

  • 불변성(Invariance)
    • 변경할 수 없는. 인스턴스가 생성된 후 값을 변경할 수 없는 타입이다.
    • 예: string
  • 가변성(Variance)
    • 변경할 수 있는. 인스턴스가 생성된 후 값이 변경될 수 있는 타입이다.
    • 예: StringBuilder

  • 여기서는 제네릭의 가변성을 살펴본다.
    • 가변성은 암묵적으로 레퍼런스를 변환할 수 있는 능력이므로, Value Type에는 사용할 수 없고, Reference Type에만 적용된다.
    • 타입 T 파라미터를 <out T>로 사용하면 공변성이 되고, <in T>로 사용하면 반공변성이 되며, in이나 out 없이 <T>로 사용하면 불변성이 된다.
    • 공변성을 지원하는 타입
      • IEnumerable<out T>, IEnumerator<out T>, IQueryable<out T>, Func<out T>
    • 반공변성을 지원하는 타입
      • IComparer<in T>, IComparable<in T>, IEqualityComparer<in T>, Action<in T>
속성 특징 파라미터
불변성 동일 타입만 할당 가능 <T>
가변성 - 공변성(Covariance) 상위 타입 할당 가능 <out T>
가변성 - 반공변성(Contravariance) 하위 타입 할당 가능 <in T>
class Animal
{
    public int Age { get; set; }
    public void Move() { }
}
class Dog : Animal
{
    public void Bark() { }
}
class K9 : Dog
{
    public void FindDrug() { }
}
 
// 제네릭 인터페이스: Covariance 
interface ICreate<out T>
{
    T Create();
}

class DogFactory : ICreate<Dog>
{
    public Dog Create()
    {
        return new Dog();
    }
}

// 제네릭 인터페이스: Contravariance
interface ITest<in T>
{
    void Run(T t);
}

class AnimalTest : ITest<Animal>
{
    public void Run(Animal t)
    {
        t.Move();
    }
}

class Main
{
    public void Run()
    {
        ICreate<Animal> creator = (ICreate<Dog>)new DogFactory();
        Animal ani = creator.Create();
        ani.Move();

        ITest<Dog> d = (ITest<Animal>)new AnimalTest();
        d.Run(new Dog());
        d.Run(new K9());
    }
}
class AnimalComparer : IComparer<Animal>
{
    int IComparer<Animal>.Compare(Animal a, Animal b)
    {
        if (a == null) return b == null ? 0 : -1;
        return b == null ? 1 : a.Age.CompareTo(b.Age);
    }
}

class Main
{
    public void Run()
    {
        // covariance 
        IEnumerable<Animal> animals = new List<Dog>() { new Dog() };
        animals.GetEnumerator().Current.Move();
 
        // contravariance 
        IComparer<Dog> dogComparer = new AnimalComparer();
        Dog dogA = new Dog { Age = 10 };
        Dog dogB = new Dog { Age = 10 };
        int result = dogComparer.Compare(dogA, dogB);
    }
}



List<T> #

  • 내부적으로는 배열을 사용한다. 현재 크기의 2배로 늘리면서 재할당한다.
  • 메모리가 순차적으로 할당이 된다. 그래서 불연속적인 메모리 할당 보다는 순차 접근이 빠르다. (캐시에 참조 지역성의 원리에 따라서 저장이 함께 되므로)
  • 대신 인덱스로 접근하기 때문에 임의 접근은 빠르다.
  • 중간에 삽입, 삭제가 효율적이지 않다. 원소들을 한칸씩 옮겨야 하기 때문이다. 그 과정에서 사본을 생성해야 하는 일도 발생한다.
메서드 기능
Add(T) 개체를 끝부분에 추가한다.
AddRang(IEnumerable\<T\>) 개체들을 끝부분에 추가한다.
Insert(Int32, T) 개체를 지정된 인덱스에 추가한다.
Remove(T) 처음 발견되는 해당 개체를 삭제한다.
RemoveAll(Predicate\<T\>) 해당 조건을 만족하는 개체들을 모두 삭제한다.
Clear() 개체들을 모두 제거한다.
Contains(T) 개체를 포함하고 있는지 확인한다.
IndexOf(T) 개체의 인덱스를 반환한다. 없으면 -1을 반환한다.
Sort() 개체들을 오름차순 혹은 내림차순으로 정렬한다.
Reverse() 개체들을 현재 상대의 반대로 만든다.
List<int> l = new List<int> { 1, 3, 2 };
        
l.Reverse(); // 2 3 1

l.Add(3); // 2 3 1 3

l.AddRange(new List<int> {6, 7, 8}); // 2 3 1 3 6 7 8

l.Remove(1); // 2 3 3 6 7 8

l.RemoveAll(x => x == 3); // 2 6 7 8 

bool res = l.Contains(9); // true

int idx = l.IndexOf(7); // 2



LinkedList<T> #

  • 이중 연결리스트로 구현되어 있다.
  • 포인터만 변경하면 되므로 중간 삽입, 삭제가 시간복잡도 1만에 이루어진다.
  • 임의접근이 안 되기 때문에 처음부터 순차적으로 살펴봐야 한다.
메서드 기능
AddFirst(T) 개체를 리스트의 맨 앞에 추가한다.
AddLast(T) 개체를 리스트의 맨 뒤에 추가한다.
AddBefore(LinkedListNode\<T\>, LinkedListNode\<T\>) 개체를 해당 인덱스 위치 앞에 추가한다.
AddAfter(LinkedListNode\<T\>, LinkedListNode\<T\>) 개체를 해당 인덱스 위치 다음에 추가한다.
Remove(T) 처음 발견되는 해당 개체를 삭제한다.
RemoveFirst() 맨 앞의 개체를 제거한다.
RemoveLast() 맨 뒤의 개체를 제거한다.
Clear() 개체들을 모두 제거한다.
Contains(T) 개체를 포함하고 있는지 확인한다.
int[] input = new[] { 1 };
LinkedList<int> ll = new LinkedList<int> (input);

ll.AddFirst(0); // 0, 1

ll.AddLast(4); // 0, 1, 4

ll.AddAfter(ll.Find(1), new LinkedListNode<int>(3)); // 0, 1, 3, 4

ll.AddBefore(ll.Find(3), new LinkedListNode<int>(2)); // 0, 1, 2, 3, 4

ll.Remove(3);  // 0, 1, 2, 4

ll.RemoveFirst(); // 1, 2, 4

ll.RemoveLast(); // 1, 2

bool res = ll.Contains(1); // true

LinkedListNode<int> node = ll.First;
for (int i = 0; i < ll.Count; i++)
{
    Console.Write(node.Value + " "); // 1, 2
    node = node.Next;
}



Dictionary<TKey, TValue> #

  • 인덱스 번호가 아닌, 키를 사용해서 값을 얻는다.
메서드 기능
Add(TKey, TValue) 개체를 추가한다.
Remove(TKey) 지정된 키를 가진 개체를 삭제한다.
Remove(TKey, TValue) 지정된 키를 가진 개체를 삭제하고, 두번째 필드에 값을 저장한다.
Clear() 개체들을 모두 제거한다.
ContainsKey(TKey) 키를 포함하고 있는지 확인한다.
ContainsValue(TValue) 값을 포함하고 있는지 확인한다.
Reverse() 개체들을 현재 상대의 반대로 만든다.
Dictionary<int, string> d = new Dictionary<int, string> { { 1, "First" }, { 2, "Second" }, { 3, "Third"} };
        
d.Add(4, "Forth"); // First, Second, Third, Forth

d.Remove(1); // Second, Third, Forth

d.Remove(2, out var deletedThis); // Third, Forth  |  deletedThis = Second

d[4] = "Fifth"; // Third, Fifth

bool resKey = d.ContainsKey(1); // false

bool resVal = d.ContainsValue("Fifth"); // true



References #