Menu

C# List: Add, Remove, Contains, Find, Sort 사용법

List<T>는 C#의 늘어나는 배열입니다. 리스트를 만들고, 항목을 추가하고 삽입하고, 값이나 인덱스나 조건으로 제거하고, Contains와 Find로 검색하고, 속성으로 정렬하고, foreach 안에서 리스트를 바꿀 때 나는 오류를 피하는 방법을 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

List<T>는 항목을 추가하고 제거함에 따라 늘어나고 줄어드는, 순서가 있는 컬렉션입니다. System.Collections.Generic에 있고, 배열처럼 인덱싱하며(list[0]), C#에서 "여러 개의 무언가"를 담을 때의 기본 선택입니다.

리스트 만들기와 항목 추가하기

꺾쇠괄호 안의 타입 매개변수가 리스트에 무엇을 담을지 정합니다: List<string>, List<int>, List<Order>.

출력:

2
Zoe, Ana, Ben, Chloe, Dev
Ana
Ana B.
3

Count는 메서드가 아니라 속성입니다(배열은 Length를, LINQ에는 Count() 메서드가 있지만 리스트에서는 속성을 쓰세요). Insert는 뒤의 모든 항목을 한 칸씩 밀므로, 큰 리스트의 앞에 삽입하면 크기에 비례하는 시간이 듭니다. 끝에 하는 Add가 저렴한 연산입니다.

항목 제거하기

항목에 대해 무엇을 아느냐에 따라 제거하는 방법이 네 가지 있습니다:

출력:

True: 85, 62, 40, 91, 55
False
62, 40, 91, 55
2 removed: 62, 91
Caught ArgumentOutOfRangeException
0

Remove는 값을 받아 처음 일치하는 것만 지웁니다. 어떤 값의 모든 복사본을 지우려면 RemoveAll(s => s == 40)을 쓰세요. 리스트에서 잘못된 인덱스는 list[i]와 RemoveAt(i) 모두에서 ArgumentOutOfRangeException을 던집니다(배열은 대신 IndexOutOfRangeException을 던집니다). RemoveRange(index, count)는 한 구간을 제거합니다.

Count와 Capacity

리스트는 항목을 내부 배열에 보관합니다. Capacity는 그 배열의 크기이고, Count는 사용 중인 칸 수입니다. Count가 Capacity에 도달하면 다음 Add가 두 배 크기의 배열을 할당하고 모든 것을 옮겨 복사합니다.

출력:

Count 0, Capacity 0
Count 1, Capacity 4
Count 2, Capacity 4
Count 3, Capacity 4
Count 4, Capacity 4
Count 5, Capacity 8
Count 6, Capacity 8
Count 7, Capacity 8
Count 8, Capacity 8
Count 9, Capacity 16
Count 0, Capacity 1000

두 배씩 늘리므로 대부분의 Add 호출은 빈 칸에 쓰기만 합니다. 대략 몇 개의 항목이 올지 안다면 생성자에 그 숫자를 넘겨 중간 복사를 건너뛰세요. new List<int>(1000)도 Count는 여전히 0이라는 점에 주의하세요. 여기서 list[0]은 예외를 던집니다. 용량은 공간이지 항목이 아닙니다.

검색: Contains, IndexOf, Find, Exists

출력:

True
2
-1
32.00
2 cheap items
False
2
True
False

Find는 첫 번째 일치 항목을 반환하고, 일치하는 것이 없으면 타입의 기본값(클래스라면 null)을 반환하므로 결과를 쓰기 전에 확인하세요. 마지막 줄이 False를 출력하는 이유는 Contains가 Equals를 쓰고, Equals를 재정의하지 않은 클래스는 참조를 비교하기 때문입니다. 필드가 같은 새 Product는 다른 객체입니다. 대신 Exists나 Find로 속성을 검색하거나, 클래스에서 Equals와 GetHashCode를 재정의하세요.

이 메서드들은 모두 리스트를 처음부터 훑으므로 호출마다 O(n)입니다. 키로 반복해서 조회한다면 Dictionary가 상수 시간에 답합니다.

리스트 정렬하기

Sort()는 항목의 자연 순서로 제자리에서 정렬합니다. 다른 기준으로 정렬하려면 비교 람다를 넘기세요.

출력:

1, 2, 5, 9
9, 5, 2, 1
Ben 95, Ana 120, Chloe 150
Chloe 150, Ben 95, Ana 120
Ben 95, Ana 120, Chloe 150

비교는 a가 먼저 와야 하면 음수, b가 먼저 와야 하면 양수, 같으면 0을 반환합니다. CompareTo가 정확히 그렇게 만들어 줍니다. Sort와 LINQ의 OrderBy 사이의 두 가지 차이가 실무에서 중요합니다:

  • Sort는 리스트를 바꾸고, OrderBy는 새 시퀀스를 반환하며 리스트는 그대로 둡니다.
  • Sort는 안정 정렬이 아니라서 같다고 비교되는 항목의 자리가 바뀔 수 있습니다. OrderBy는 안정 정렬이며, ThenBy로 두 번째 키를 추가합니다: players.OrderBy(p => p.Score).ThenBy(p => p.Name).

직접 만든 클래스의 리스트에 비교 없이 Sort()를 호출하면 리스트가 항목의 순서를 정하는 방법을 모르므로 InvalidOperationException을 던집니다. 비교를 넘기거나 클래스에 IComparable<T>를 구현하세요.

Reverse()는 제자리에서 뒤집습니다. System.Linq에도 Reverse 확장 메서드가 있지만, List<T>에서 list.Reverse()는 아무것도 반환하지 않는 제자리 버전을 고릅니다.

순회하기, 그리고 순회 중 제거하기

foreach는 모든 항목을 순서대로 읽습니다. for 루프는 인덱스도 줍니다. 할 수 없는 것은 같은 리스트에 대한 foreach 도중 항목을 추가하거나 제거하는 것입니다:

출력:

Caught InvalidOperationException
120, 80, 60
120, 80, 60

예외는 "Collection was modified; enumeration operation may not execute." 메시지를 가진 InvalidOperationException입니다. 앞으로 가는 for 루프는 예외를 던지지 않지만, 모든 것이 한 인덱스씩 당겨지므로 제거한 항목 바로 다음 항목을 조용히 건너뜁니다. 거꾸로 돌면 이를 피할 수 있습니다. 세 번째 해결책은 복사본을 순회하는 것입니다: foreach (var t in orders.ToList()).

foreach 안에서 항목의 속성을 바꾸는 것(예를 들어 order.Status = "sent")은 괜찮습니다. 항목을 추가하거나 제거하면 열거자가 깨지고, 인덱서로 교체하는 것(orders[i] = 0)도 마찬가지입니다.

리스트와 배열 사이의 변환

출력:

4
3
4, 4, 4, 4
Oslo / Lima / Pune / Kyiv

양방향 모두 요소를 새 저장소에 복사하므로, 나중에 리스트를 바꿔도 배열에는 영향이 없습니다. ConvertAll은 LINQ의 Select(...).ToList()에 해당하는 리스트 자체의 메서드입니다. Console.WriteLine(list)로 리스트를 출력하면 타입 이름(System.Collections.Generic.List`1[System.String])이 나오므로 string.Join을 쓰세요.

빠른 참조

작업코드
만들기var l = new List<int>(); 또는 new List<int> { 1, 2 }
끝에 추가l.Add(x), l.AddRange(items)
위치에 삽입l.Insert(i, x)
처음 일치 항목 제거l.Remove(x)(bool 반환)
인덱스로 제거l.RemoveAt(i)
조건으로 제거l.RemoveAll(x => ...)(개수 반환)
항목 수l.Count
포함 여부l.Contains(x), l.Exists(x => ...)
찾기l.Find(...), l.FindAll(...), l.FindIndex(...)
제자리 정렬l.Sort(), l.Sort((a, b) => ...)
정렬된 복사본l.OrderBy(x => ...).ToList()
배열로l.ToArray()

흔한 실수

  • foreach 안에서 제거하기. InvalidOperationException이 발생합니다. RemoveAll이나 거꾸로 가는 for 루프를 쓰세요.
  • Remove(x)가 모든 복사본을 제거하리라 기대하기. 첫 번째만 제거합니다. RemoveAll을 쓰세요.
  • Equals 없는 객체에 Contains 쓰기. 참조를 비교합니다. Exists로 속성을 검색하세요.
  • 용량을 지정해 만든 리스트에 인덱싱하기. new List<int>(10)은 비어 있습니다. 먼저 항목을 추가하세요.
  • Sort가 같은 항목의 순서를 유지하리라 가정하기. 유지하지 않습니다. 같은 항목 사이의 순서가 중요하면 OrderBy를 쓰세요.
  • 공유하려고 리스트를 대입한 뒤 변경에 놀라기. var copy = list;는 같은 리스트이고, new List<T>(list)가 복사본입니다.

자주 묻는 질문

C#에서 List에 항목을 추가하려면 어떻게 하나요?

list.Add(item)은 끝에 항목 하나를 덧붙이고, list.AddRange(otherCollection)은 여러 개를 덧붙이며, list.Insert(index, item)은 특정 위치에 항목을 넣고 나머지를 뒤로 밉니다. 만들 때 채울 수도 있습니다: var names = new List<string> { "Ana", "Ben" };.

C#에서 List의 항목을 제거하려면 어떻게 하나요?

Remove(value)는 처음 일치하는 항목을 제거하고 찾았으면 true를 반환합니다. RemoveAt(index)는 위치로 제거합니다. RemoveAll(x => condition)은 일치하는 모든 항목을 제거하고 제거한 개수를 반환합니다. Clear()는 리스트를 비웁니다.

C#에서 List에 값이 들어 있는지 어떻게 확인하나요?

list.Contains(value)는 true나 false를 반환합니다. Equals로 비교하므로, 직접 만든 클래스는 Equals를 재정의하지 않는 한 참조를 비교합니다. 속성으로 확인하려면 list.Exists(p => p.Name == "Ana")나 LINQ의 list.Any(...)를 쓰세요.

C#에서 List를 속성으로 정렬하려면 어떻게 하나요?

Sort에 비교를 넘기세요: people.Sort((a, b) => a.Age.CompareTo(b.Age))는 제자리에서 정렬합니다. 내림차순은 a와 b를 바꾸세요. 또는 LINQ를 쓰면 정렬된 새 시퀀스를 반환하고 같은 항목의 원래 순서를 유지합니다: people.OrderBy(p => p.Age).ToList().

"Collection was modified; enumeration operation may not execute" 오류가 나는 이유는 무엇인가요?

foreach가 같은 리스트를 순회하는 동안 항목을 추가하거나, 제거하거나, 교체(list[i] = x)해서 열거자가 다음 단계에서 InvalidOperationException을 던진 것입니다. list.RemoveAll(condition)을 쓰거나, for 루프로 거꾸로 돌거나, list.ToList() 같은 복사본을 순회하세요.

Count와 Capacity의 차이는 무엇인가요?

Count는 리스트의 항목 수입니다. Capacity는 내부 배열의 크기로, 최소한 Count 이상입니다. 배열이 가득 차면 리스트는 두 배 크기의 새 배열을 할당하고 항목을 복사하므로, Add는 평균적으로 빠릅니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기