C#에서 순서가 있는 데이터를 담고 실행 중 항목을 추가하거나 삭제해야 한다면 List<T>가 가장 먼저 떠올릴 수 있는 컬렉션이에요. T 자리에 int, string, 사용자 정의 클래스 같은 실제 형식을 지정하므로 잘못된 값은 컴파일 단계에서 걸러지고, 꺼낼 때마다 형 변환할 필요도 없습니다.
크기가 고정된 데이터는 배열, 크기가 변하는 순차 데이터는
List<T>, 키로 값을 자주 찾는 데이터는 Dictionary<TKey,TValue>가 어울립니다. List<T>는 인덱스 접근과 순회가 편하지만 목록 앞이나 중간의 잦은 삽입·삭제에는 비용이 생겨요.List<T>에서 T는 무엇을 뜻할까요?
T는 나중에 실제 형식으로 바뀌는 형식 매개변수입니다. List<int>는 정수만, List<string>은 문자열만 보관해요. 예전의 ArrayList는 여러 형식을 한 목록에 넣을 수 있어 값을 꺼낼 때 캐스팅이 필요했고, 값 형식은 boxing과 unboxing이 발생할 수 있었습니다. 현재 .NET 코드에서는 특별한 이유가 없다면 형식 안전성이 있는 제네릭 컬렉션을 사용하는 편이 좋습니다.
using System.Collections.Generic;
List<string> tasks = new();
tasks.Add("로그 확인");
tasks.Add("백업 검증");
string firstTask = tasks[0];
Console.WriteLine(firstTask);
최신 C# 프로젝트에서는 암시적 전역 using 설정에 따라 using System.Collections.Generic;이 없어도 컴파일될 수 있습니다. 하지만 코드 조각을 다른 프로젝트로 옮길 때는 네임스페이스가 필요할 수 있으니, 오류가 난다면 먼저 using 선언과 대상 프레임워크 설정을 확인하세요.

추가·삽입·삭제는 어떻게 구분할까요?
Add는 목록 끝에 항목을 붙이고, Insert는 지정한 인덱스에 항목을 끼워 넣습니다. 삭제할 때 Remove(value)는 일치하는 첫 값을 찾아 제거하고 성공 여부를 bool로 돌려줘요. RemoveAt(index)는 값이 아니라 위치를 삭제합니다. 이름은 비슷하지만 기준이 다르므로 혼동하기 쉽습니다.
List<int> numbers = [0, 1, 2, 3, 4];
numbers.RemoveAt(2); // 인덱스 2의 값 2 삭제
numbers.Insert(2, 20); // 인덱스 2에 값 20 삽입
bool removed = numbers.Remove(4); // 값 4 삭제
foreach (int number in numbers)
{
Console.WriteLine(number);
}
인덱스는 0부터 시작합니다. Count가 5라면 유효한 마지막 인덱스는 4예요. 범위를 벗어난 인덱스로 읽거나 RemoveAt을 호출하면 ArgumentOutOfRangeException이 발생할 수 있습니다. 외부 입력으로 인덱스를 받는다면 index >= 0 && index < list.Count를 먼저 확인하는 편이 안전합니다.
List<T>와 배열은 언제 바꿔 써야 할까요?
배열은 생성할 때 길이가 정해지고 이후 길이를 바꿀 수 없습니다. 반면 List<T>는 내부 배열의 용량을 필요에 따라 늘리며 항목을 추가합니다. 데이터 개수가 실행 중 달라지고 순서대로 다뤄야 한다면 List가 편해요. 길이가 고정되어 있고 메모리 배치가 단순해야 한다면 배열이 더 명확할 수 있습니다.
Count는 현재 담긴 항목 수이고 Capacity는 새로 할당하지 않고 담을 수 있는 내부 공간입니다. 많은 항목이 들어올 것을 미리 안다면 생성자에서 용량을 지정해 재할당 횟수를 줄일 수 있지만, 실제 측정 없이 큰 값을 습관적으로 잡을 필요는 없습니다.
검색과 정렬에서 놓치기 쉬운 부분
Contains, IndexOf, Remove는 항목의 같음을 비교합니다. 사용자 정의 클래스의 동일성 기준이 업무 규칙과 다르면 IEquatable<T> 구현이나 별도의 검색 조건을 검토해야 해요. Sort 역시 기본 비교 규칙이 없으면 원하는 순서로 정렬되지 않으므로 비교자나 람다식을 명시할 수 있습니다.
List<string> names = ["민준", "서연", "지우"];
bool exists = names.Contains("서연");
names.Sort(StringComparer.Ordinal);
string? found = names.Find(name => name.StartsWith("지"));
여러 스레드가 동시에 수정한다면 주의하세요
List<T>는 여러 스레드가 동시에 항목을 추가하거나 삭제하는 상황을 자동으로 보호하지 않습니다. 동시에 수정해야 한다면 잠금 범위를 명확히 하거나 System.Collections.Concurrent의 목적에 맞는 컬렉션을 검토해야 해요. 읽기 전용으로 외부에 노출하려는 경우에도 원본 List를 그대로 반환하기보다 읽기 전용 인터페이스나 복사본을 선택할 수 있습니다.
공식 자료
최초 작성: 2015년 1월 13일 · 내용 및 예제 업데이트: 2026년 8월 24일
0 댓글