[로봇활용_12주차] C# LINQ select의 반환 타입

최윤호·2025년 10월 30일
post-thumbnail

select는 무엇을 돌려주는 걸까?

LINQ를 사용하다 보면 select는 정말 밥 먹듯이 사용하게 되는 단골손님이죠.
"이 리스트에서 이름만 뽑아줘!"와 같은 원하는 데이터만 쏙쏙 골라내는 역할을 하니까요.
그런데 select가 우리에게 돌려주는 결과물에 대해 깊이 생각해 보신 적 있나요?
select를 실행하면 List<T>가 나올까요? 아니면 Array일까요?
이번 글에서는 LINQ의 핵심 중 하나인 select가 반환하는 타입,
IEnumerable<T>에 대해 깊이 파고들어 보고, 이것이 왜 중요한지 알아보겠습니다.

1)select의 역할: 프로젝션

본격적으로 알아보기 전에 select의 역할을 다시 한번 짚고 넘어가죠.
select는 컬렉션의 각 요소를 새로운 형태로 변환하는 역할을 합니다.

비유: select는 일종의 '가공 공장'과 같아요. List<Player>라는
원재료가 컨베이어 벨트를 타고 들어오면, select공장은 각 재료를
'이름표(string)'라는 완제품으로 가공해서 내보내는 거죠.
중요한 점은, 이 과정에서 원본 재료는 손상되지 않는다는 것입니다.

[코드]

public class Player
{
    public string Name { get; set; }
    public int Level { get; set; }
}

class Program
{
    static void Main()
    {
        var players = new List<Player>
        {
            new Player { Name = "홍길동", Level = 45 },
            new Player { Name = "이순신", Level = 50 }
        };

        // players 리스트에서 각 player의 Name(string)만 선택
        var playerNames = players.Select(player => player.Name);

        foreach (var name in playerNames)
        {
            Console.WriteLine($"플레이어: {name}");
        }
    }
}

[실행 결과]

플레이어: 홍길동
플레이어: 이순신

2)반환 타입의 정체

위 코드에서 playerNames 변수의 타입은 무엇일까요?
정답은 IEnumerable<string>입니다.
select메서드의 실제 정의(시그니처)를 보면 그 이유를 명확하게 알 수 있습니다.

public static IEnumerable<TResult> Select<TSource, TResult>(
    this IEnumerable<TSource> source, 
    Func<TSource, TResult> selector
);

3)select 메서드 시그니처 분석

  • this IEnumerable<TSource> source: players같은 원본 데이터 소스입니다.
    List<T>나 배열 등 IEnumerable<T>를 구현하는 모든 컬렉션이 해당합니다.
  • Func<TSource, TResult> selector: player => player.Name처럼,
    원본 요소(TSourcePlayer)를 결과 요소(TResultstring)로
    변환하는 방법을 정의한 람다식입니다.
  • IEnumerable<TResult>: 바로 이 부분이 반환 타입입니다!
    변환된 결과(TResult)들을 담고 있는 새로운 시퀀스를 반환하죠.

IEnumerable<T>List<T>Array처럼 데이터가 메모리에
완전히 담겨있는 '컬렉션'이라기보다는, "요청하면 데이터를 하나씩
순서대로 줄 수 있는 능력"을 가진 '데이터의 목록'에 가깝습니다.

이것이 바로 LINQ의 핵심 특징인 지연 실행(Deferred Execution)과 연결됩니다.
select를 호출하는 순간 모든 데이터를 변환해서 메모리에 올려두는 게 아니라,
foreach로 순회하는 것처럼 데이터가 필요한 시점에 변환 작업을 시작합니다.

비유: IEnumerable<T>은 유튜브의 '재생 목록'과 같아요.
'재생 목록'에 100개의 영상을 담아뒀다고 해서
100개의 영상이 전부 내 컴퓨터에 다운로드된 상태는 아니죠.
내가 '재생 목록'에서 영상을 누르는 순간 스트리밍이 시작되는 것과 같습니다.

4)TResult의 무한한 가능성

select의 강력함은 TResult, 즉 결과 타입을 우리 마음대로 정할 수 있다는 점에서 나옵니다.

1. 다른 타입으로 변환

가장 흔한 경우입니다. 객체에서 특정 속성만 뽑아내는 경우죠.

// Player 객체에서 Name(string)만 선택
// TSource: Player, TResult: string
IEnumerable<string> playerNames = players.Select(player => player.Name);

2. 같은 타입으로 변환

원본과 같은 타입의 새로운 객체를 만들어 반환할 수도 있습니다. 예를 들어,
모든 플레이어의 레벨을 1씩 올린 새로운 플레이어 목록을 만들고 싶을 때 유용합니다.
(원본 players리스트는 변경되지 않아요!)

// 모든 플레이어의 레벨을 1 올린 '새로운' Player 객체 생성
// TSource: Player, TResult: Player
IEnumerable<Player> leveledUpPlayers = players.Select(player => new Player
{
    Name = player.Name,
    Level = player.Level + 1
});

3. 익명 타입으로 변환

정식으로 클래스를 정의하기엔 번거롭고, 잠시 사용할 데이터 묶음이 필요할 때가 있습니다.
이럴 땐 익명 타입(new { ... })을 사용해 즉석에서 새로운 형태의 객체를 만들 수 있습니다.

// 이름과, 레벨이 40 이상인지 여부를 담는 새로운 객체 생성
// TSource: Player, TResult: Anonymous Type
var playerStatus = players.Select(player => new 
{
    PlayerName = player.Name,
    IsHighLevel = player.Level >= 40
});

foreach (var status in playerStatus)
{
    Console.WriteLine($"이름: {status.PlayerName}, 고레벨: {status.IsHighLevel}");
}

5)정리

오늘은 select의 반환 타입에 대해 깊이 있게 알아봤습니다.
이제 LINQ에서 select를 보면 단순히 '데이터를 뽑는다.'를 넘어
'새로운 형태의 IEnumerable<T>시퀀스를 반환한다.'라고 생각할 수 있을 거예요.

개념설명
select의 역할컬렉션의 각 요소를 새로운 형태로 변환(Projection)합니다.
반환 타입항상 IEnumerable<TResult>입니다. List<T>가 아니에요!
핵심 특징지연 실행(Deferred Execution). 데이터가 실제로 필요할 때까지 변환 작업을 미룹니다.
TResult의 유연성원본과 다른 타입, 같은 타입, 심지어 즉석에서 만든 익명 타입까지 무엇이든 될 수 있습니다.
profile
🚀 미래의 엔지니어를 꿈꾸는 훈련생의 기록 📝

0개의 댓글