1. 소개
쇼핑 리스트를 만든다고 상상해봐. 오늘은 3개만 사면 되고, 내일은 10개, 모레는 1개만 필요해. 만약 배열을 쓴다면, 매번 필요한 개수만큼 새 배열을 만들어야 해. 아니면 "혹시 몰라서" 엄청 큰 배열을 만들고 빈 칸을 잔뜩 들고 다녀야겠지. 별로 효율적이지 않지?
근데 만약에 마법 가방이 있어서, 물건을 넣으면 자동으로 커지고, 빼면 작아진다고 생각해봐. 몇 개 넣을지 미리 몰라도 돼. 그냥 넣으면 가방이 알아서 맞춰줘. 이게 바로 C# 프로그래밍 세계에서 List<T>야!
List<T>는 특정 타입의 요소들을 순서대로 저장할 수 있는 제네릭 컬렉션이야. 여기서 핵심은 "동적"이라는 거. 배열이랑 다르게 List<T>는 실행 중에 크기를 바꿀 수 있어. 내부적으로 용량을 알아서 관리하고, 필요하면 늘려줘.
List<T>에서 T는 뭐냐고? 이건 타입 파라미터야. List를 만들 때 T 대신 어떤 타입을 저장할지 정하는 거지. 예를 들어, List<int>는 정수만, List<string>은 문자열만, List<double>은 실수만 저장할 수 있어. 그래서 오렌지 리스트에 실수로 사과를 넣는 일은 없지. 이게 코드의 안정성과 예측 가능성을 보장해줘.
이게 왜 필요할까?
실제 프로젝트에서는 요소 개수를 미리 모르는 경우가 진짜 많아. 예를 들면:
- 사이트에 접속한 사용자 리스트
- 온라인 쇼핑몰 장바구니 상품 리스트
- 검색 결과 리스트
- 할 일 관리 앱의 할 일 리스트 (이걸 지금 만들 거야!)
이런 경우엔 요소 개수가 계속 바뀌니까 List<T>가 완전 필수야.
List<T>의 주요 메서드
| 메서드 | 설명 | 예시 |
|---|---|---|
|
리스트 끝에 요소 추가 | |
|
지정한 위치에 삽입 | |
|
첫 번째로 나오는 요소 삭제 | |
|
인덱스로 요소 삭제 | |
|
리스트 비우기 | |
|
요소가 있는지 확인 | |
|
첫 번째로 나오는 인덱스 | |
|
리스트에 있는 요소 개수 | |
|
내부 버퍼 용량 | |
2. 리스트(List<T>) 만들기
실전으로 가보자! 간단한 할 일 관리 앱을 만든다고 해보자. 할 일을 추가할 리스트가 필요하지.
새 List<T>를 만들 때는 new 연산자를 써. 다른 객체 만들 때랑 똑같아.
List 선언하고 만들기
리스트 만드는 문법은 배열이랑 비슷한데, 대괄호 대신 꺾쇠(<>)를 써:
using System.Collections.Generic;
List<int> numbers = new List<int>();
상세 설명 좋아하는 사람을 위한 코멘트:
- List<int>는 정수 리스트야(int). int 대신 string, double, 직접 만든 클래스 등 아무 타입이나 넣을 수 있어.
- 만들자마자 리스트는 비어있어 — 길이는 0이야.
예시: 우리 앱의 첫 단계
예를 들어, 예전 강의에서 사용자 이름이랑 나이만 입력받던 앱이 있다고 해보자. 이제 여러 이름을 리스트에 저장해서 나중에 쓸 수 있게 해보자.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> names = new List<string>();
Console.WriteLine("이름 세 개를 입력하세요:");
for (int i = 0; i < 3; i++)
{
string name = Console.ReadLine();
names.Add(name);
}
Console.WriteLine("입력한 이름:");
foreach (string n in names)
{
Console.WriteLine(n);
}
}
}
여기서 Add()에 주목! 이 메서드가 리스트에 새 요소를 추가해. 이게 바로 리스트를 동적으로 만들어주는 거지!
처음부터 요소가 있는 리스트도 만들 수 있어!
List<string> planets = new List<string> { "수성", "금성", "지구" };
처음부터 "스타팅 멤버"를 알고 있을 때 완전 편해. 테스트나 고정 데이터 초기화할 때 자주 써.
3. List<T>의 기본 조작
트릭이나 마법 쓰기 전에, 리스트의 "기본기"부터 익혀보자.
요소 추가: Add()
Add() 메서드는 새 물건을 선반에 올리는 거랑 똑같아:
List<int> numbers = new List<int>();
numbers.Add(42);
numbers.Add(100);
이제 내부에는: [42, 100]
요소 개수 얻기: Count
배열이랑 다르게 리스트는 Length가 아니라 Count라는 속성을 써:
int count = numbers.Count; // 2 반환
기억해: 리스트는 Count, 배열/문자열은 Length야. 비슷해 보여도 다른 속성이야.
인덱스로 요소 접근
방법은 두 가지, 사실상 똑같아:
int first = numbers[0]; // 42
numbers[1] = 200; // 100을 200으로 바꿈
중요: 인덱스는 배열처럼 0부터 시작해. 요소가 3개면 numbers[3]에 접근하면 안 돼!
리스트 요소 순회
for나 foreach 둘 다 쓸 수 있어:
// 인덱스가 필요하면 for
for (int i = 0; i < numbers.Count; i++)
{
Console.WriteLine(numbers[i]);
}
// 인덱스 필요 없으면 foreach
foreach (int number in numbers)
{
Console.WriteLine(number);
}
foreach가 보통 더 쉽고 직관적이야, 특히 큰 컬렉션에서.
근데 foreach에서는 인덱스로 요소를 바꾸거나 break로 중간에 나가려면 추가적인 코드가 필요해.
인덱스로 요소 삽입: Insert()
Insert()는 끝이 아니라 아무 위치에나 요소를 넣고 싶을 때 써. 예를 들어 두 번째 자리에:
numbers.Insert(1, 55); // 인덱스 1에 55 삽입
이제 numbers: [42, 55, 200]
요소 삭제
삭제도 추가만큼 쉬워:
numbers.Remove(55); // 55의 첫 번째 등장 삭제
numbers.RemoveAt(0); // 인덱스 0 요소 삭제
numbers.Clear(); // 리스트 전체 비우기
삭제할 때 주의:
- Remove로 없는 값을 지우려 해도 아무 일도 안 일어나. 리스트는 그대로고, 에러도 없어.
- Clear() 후엔 리스트가 다시 비어있어.
4. 유용한 팁
List<T>는 어떻게 커질까?
List<T>에 요소를 추가할 때마다 크기가 조금씩 늘어나는 게 아니야. 대신 List<T>는 미리 여유 있는 메모리 블록(내부 용량, Capacity)을 잡아둬.
요소가 현재 용량을 넘어서면, 더 큰 메모리 블록(보통 2배 크기)을 새로 만들고, 기존 데이터를 복사해:
요소 추가 → 들어감 → 들어감 → 들어감 →
새 요소가 안 들어감 → 더 큰 배열 생성 → 데이터 복사 → 계속 추가
이렇게 하면 Add할 때마다 메모리를 재할당하지 않아도 돼서 더 빨라.
var list = new List<int>(); // Capacity = 0
list.Add(1); // Capacity = 4
list.Add(2); // Capacity = 4
list.Add(3); // Capacity = 4
list.Add(4); // Capacity = 4
list.Add(5); // Capacity = 8 (확장!)
Capacity vs. Count: 뭐가 다를까?
- Count — 리스트에 실제로 들어있는 요소 개수
- Capacity — 요소를 저장하는 내부 배열의 크기
List<int> nums = new List<int>();
Console.WriteLine(nums.Count); // 0
Console.WriteLine(nums.Capacity); // 보통 0 또는 작은 값
nums.Add(123);
Console.WriteLine(nums.Count); // 1
Console.WriteLine(nums.Capacity); // 이제 Count보다 클 수도 있음
nums.Capacity = 1000; // 요소가 많을 걸 알면 Capacity를 미리 늘릴 수도 있어
대부분의 개발자는 Count만 쓰지만, 성능이 중요한 앱(예: 게임, 데이터 분석 등)에서는 Capacity를 조절하는 게 유용할 수 있어.
5. 예시
우리 앱을 좀 더 발전시켜보자 — 이번엔 사용자가 좋아하는 숫자를 추가하고, 리스트에서 삽입/삭제/출력/초기화까지 해보자.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> favoriteNumbers = new List<int>();
Console.WriteLine("좋아하는 숫자를 입력하세요 (한 줄에 하나씩, 0 입력하면 종료):");
while (true)
{
int num = int.Parse(Console.ReadLine());
if (num == 0) break;
favoriteNumbers.Add(num);
}
Console.WriteLine("당신이 좋아하는 숫자:");
foreach (int n in favoriteNumbers)
{
Console.Write(n + " ");
}
Console.WriteLine("\n첫 번째 숫자를 삭제할까요? (y/n)");
if (Console.ReadLine().ToLower() == "y")
{
favoriteNumbers.RemoveAt(0);
}
Console.WriteLine("지금 리스트:");
foreach (int n in favoriteNumbers)
{
Console.Write(n + " ");
}
}
}
참고: 여기서는 int.Parse를 썼어. 실제 프로젝트에서는 int.TryParse로 입력값을 꼭 체크해서, 사용자가 잘못 입력해도 프로그램이 안 죽게 하는 게 좋아.
6. 초보자가 자주 하는 실수
이런 경우 진짜 많아: 0부터 List.Count까지 반복문을 돌리면서(foreach 등) 리스트를 동시에 수정(추가/삭제)하는 거. 이러면 안 돼! 프로그래밍 고수들은 이걸 "순회 중 컬렉션 수정"이라고 부르는데, 대부분 바로 InvalidOperationException 예외가 터져.
나쁜 코드 예시:
foreach (int n in numbers)
{
if (n < 0)
numbers.Remove(n); // 펑! 런타임 에러.
}
정답: 삭제할 인덱스/요소를 따로 리스트에 모아두거나, for문을 거꾸로 돌려서 삭제해야 해.
GO TO FULL VERSION