다음을 통해 공유


프로젝션 작업(C#)

프로젝션은 종종 나중에 사용되는 속성으로만 구성되는 새 형식으로 개체를 변환하는 작업을 나타냅니다. 프로젝션을 사용하면 각 개체를 기반으로 만들어지는 새 형식을 생성할 수 있습니다. 속성을 프로젝션하고 속성에서 수학 함수를 수행할 수 있습니다. 원래 개체를 변경하지 않고 프로젝션할 수도 있습니다.

Important

이 샘플은 System.Collections.Generic.IEnumerable<T> 데이터 원본을 사용합니다. System.Linq.IQueryProvider 기반 데이터 원본은 System.Linq.IQueryable<T> 데이터 원본과 식 트리를 사용합니다. 식 트리에는 허용되는 C# 구문에 대한 제한 사항이 있습니다. 또한 EF Core와 같은 각 IQueryProvider 데이터 원본에는 더 많은 제한이 적용될 수 있습니다. 데이터 원본에 대한 설명서를 확인합니다.

다음 섹션에는 프로젝션을 수행하는 표준 쿼리 연산자 메서드가 나와 있습니다.

메서드

메서드 이름 설명 C# 쿼리 식 구문 자세한 정보
선택 변환 함수를 기반으로 하는 값을 프로젝션합니다. select Enumerable.Select
Queryable.Select
SelectMany 변환 함수를 기반으로 하는 값의 시퀀스를 프로젝션한 다음 하나의 시퀀스로 평면화합니다. 여러 from 절 사용 Enumerable.SelectMany
Queryable.SelectMany
Zip 지정된 2~3개 시퀀스의 요소를 사용하여 튜플 시퀀스를 생성합니다. 해당 사항 없음 Enumerable.Zip
Queryable.Zip

Select

다음 예제에서는 select 절을 사용하여 문자열 목록의 각 문자열에서 첫 글자를 프로젝션합니다.

List<string> words = ["an", "apple", "a", "day"];

var query = from word in words
            select word.Substring(0, 1);

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    a
    a
    a
    d
*/

메서드 구문을 사용하는 동일한 쿼리는 다음 코드에 나와 있습니다.

List<string> words = ["an", "apple", "a", "day"];

var query = words.Select(word => word.Substring(0, 1));

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    a
    a
    a
    d
*/

SelectMany

다음 예제에서는 여러 from 절을 사용하여 문자열 목록의 각 문자열에서 각 단어를 프로젝션합니다.

List<string> phrases = ["an apple a day", "the quick brown fox"];

var query = from phrase in phrases
            from word in phrase.Split(' ')
            select word;

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    an
    apple
    a
    day
    the
    quick
    brown
    fox
*/

메서드 구문을 사용하는 동일한 쿼리는 다음 코드에 나와 있습니다.

List<string> phrases = ["an apple a day", "the quick brown fox"];

var query = phrases.SelectMany(phrases => phrases.Split(' '));

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    an
    apple
    a
    day
    the
    quick
    brown
    fox
*/

SelectMany 메서드는 첫 번째 시퀀스의 모든 항목을 두 번째 시퀀스의 모든 항목과 일치시키는 조합을 형성할 수도 있습니다.

var query = from number in numbers
            from letter in letters
            select (number, letter);

foreach (var item in query)
{
    Console.WriteLine(item);
}

메서드 구문을 사용하는 동일한 쿼리는 다음 코드에 나와 있습니다.

var method = numbers
    .SelectMany(number => letters,
    (number, letter) => (number, letter));

foreach (var item in method)
{
    Console.WriteLine(item);
}

Zip

Zip 프로젝션 연산자에 대한 오버로드가 여러 개 있습니다. 모든 Zip 메서드는 둘 이상의 이종 형식의 시퀀스에 대해 작동합니다. 처음 두 오버로드는 지정된 시퀀스의 해당 위치 형식과 함께 튜플을 반환합니다.

다음과 같은 컬렉션을 생각해 보겠습니다.

// An int array with 7 elements.
IEnumerable<int> numbers = [1, 2, 3, 4, 5, 6, 7];
// A char array with 6 elements.
IEnumerable<char> letters = ['A', 'B', 'C', 'D', 'E', 'F'];

이러한 시퀀스를 함께 프로젝션하려면 Enumerable.Zip<TFirst,TSecond>(IEnumerable<TFirst>, IEnumerable<TSecond>) 연산자를 사용합니다.

foreach ((int number, char letter) in numbers.Zip(letters))
{
    Console.WriteLine($"Number: {number} zipped with letter: '{letter}'");
}
// This code produces the following output:
//     Number: 1 zipped with letter: 'A'
//     Number: 2 zipped with letter: 'B'
//     Number: 3 zipped with letter: 'C'
//     Number: 4 zipped with letter: 'D'
//     Number: 5 zipped with letter: 'E'
//     Number: 6 zipped with letter: 'F'

중요

zip 작업의 결과 시퀀스는 길이가 최단 시퀀스보다 길지 않습니다. numbersletters 컬렉션은 길이가 다르며, 결과 시퀀스에서는 numbers 컬렉션의 마지막 요소가 생략됩니다. 압축할 항목이 없기 때문입니다.

두 번째 오버로드는 third 시퀀스를 허용합니다. 또 다른 컬렉션 emoji를 만들어 보겠습니다.

// A string array with 8 elements.
IEnumerable<string> emoji = [ "🤓", "🔥", "🎉", "👀", "⭐", "💜", "✔", "💯"];

이러한 시퀀스를 함께 프로젝션하려면 Enumerable.Zip<TFirst,TSecond,TThird>(IEnumerable<TFirst>, IEnumerable<TSecond>, IEnumerable<TThird>) 연산자를 사용합니다.

foreach ((int number, char letter, string em) in numbers.Zip(letters, emoji))
{
    Console.WriteLine(
        $"Number: {number} is zipped with letter: '{letter}' and emoji: {em}");
}
// This code produces the following output:
//     Number: 1 is zipped with letter: 'A' and emoji: 🤓
//     Number: 2 is zipped with letter: 'B' and emoji: 🔥
//     Number: 3 is zipped with letter: 'C' and emoji: 🎉
//     Number: 4 is zipped with letter: 'D' and emoji: 👀
//     Number: 5 is zipped with letter: 'E' and emoji: ⭐
//     Number: 6 is zipped with letter: 'F' and emoji: 💜

이전 오버로드와 마찬가지로 Zip 메서드가 튜플을 프로젝션하지만 이번에는 세 개의 요소가 있습니다.

세 번째 오버로드는 결과 선택기 역할을 하는 Func<TFirst, TSecond, TResult> 인수를 허용합니다. 압축되는 시퀀스에서 새 결과 시퀀스를 예상할 수 있습니다.

foreach (string result in
    numbers.Zip(letters, (number, letter) => $"{number} = {letter} ({(int)letter})"))
{
    Console.WriteLine(result);
}
// This code produces the following output:
//     1 = A (65)
//     2 = B (66)
//     3 = C (67)
//     4 = D (68)
//     5 = E (69)
//     6 = F (70)

앞의 Zip 오버로드를 사용하여 지정된 함수를 해당 요소 numbersletter에 적용하여 string 결과의 시퀀스를 생성합니다.

SelectSelectMany

SelectSelectMany 둘 다의 작업은 소스 값에서 결과 값을 생성하는 것입니다. Select는 모든 소스 값에 대해 하나의 결과 값을 생성합니다. 따라서 전체 결과는 소스 컬렉션과 동일한 개수의 요소가 들어 있는 컬렉션입니다. 반면, SelectMany은(는) 각 원본 값에서 연결된 하위 컬렉션을 포함하는 단일 전체 결과를 생성합니다. SelectMany에 대한 인수로 전달되는 변환 함수는 각 소스 값에 대해 열거 가능한 값 시퀀스를 반환해야 합니다. SelectMany은(는) 이러한 열거 가능한 시퀀스를 연결하여 하나의 큰 시퀀스를 만듭니다.

다음 두 그림은 이러한 두 메서드의 작업 간에 개념적 차이를 보여 줍니다. 각각의 경우에서 선택기(변환) 함수는 각 소스 값에서 꽃의 배열을 선택한다고 가정합니다.

이 그림은 Select에서 소스 컬렉션과 동일한 개수의 요소가 들어 있는 컬렉션을 반환하는 방법을 보여 줍니다.

Select() 작업을 보여주는 그래픽

이 그림은 SelectMany에서 배열의 중간 시퀀스를 각 중간 배열의 각 값이 포함된 하나의 최종 결과 값으로 연결하는 방법을 보여 줍니다.

SelectMany()의 동작을 보여주는 그래픽

코드 예제

다음 예제에서는 SelectSelectMany의 동작을 비교합니다. 코드는 소스 컬렉션의 각 꽃 이름 목록에서 해당 항목을 사용하여 꽃 ‘부케’를 만듭니다. 다음 예제에서는 변환 함수 Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>)에서 사용하는 "단일 값"은 값의 컬렉션입니다. 이 예제에서는 각 하위 시퀀스의 각 문자열을 열거하기 위해 추가 foreach 루프가 필요합니다.

class Bouquet
{
    public required List<string> Flowers { get; init; }
}

static void SelectVsSelectMany()
{
    List<Bouquet> bouquets =
    [
        new Bouquet { Flowers = ["sunflower", "daisy", "daffodil", "larkspur"] },
        new Bouquet { Flowers = ["tulip", "rose", "orchid"] },
        new Bouquet { Flowers = ["gladiolis", "lily", "snapdragon", "aster", "protea"] },
        new Bouquet { Flowers = ["larkspur", "lilac", "iris", "dahlia"] }
    ];

    IEnumerable<List<string>> query1 = bouquets.Select(bq => bq.Flowers);

    IEnumerable<string> query2 = bouquets.SelectMany(bq => bq.Flowers);

    Console.WriteLine("Results by using Select():");
    // Note the extra foreach loop here.
    foreach (IEnumerable<string> collection in query1)
    {
        foreach (string item in collection)
        {
            Console.WriteLine(item);
        }
    }

    Console.WriteLine("\nResults by using SelectMany():");
    foreach (string item in query2)
    {
        Console.WriteLine(item);
    }
}

참고 항목