CodeGym /행동 /C# SELF /LINQ의 새로운 메서드: CountBy

LINQ의 새로운 메서드: CountByAggregateBy

C# SELF
레벨 32 , 레슨 2
사용 가능

1. 소개

혹시 "클래식" LINQ 쿼리에서 그룹화를 써본 적 있다면, 예를 들어:


var cityCounts = students.GroupBy(s => s.City)
                         .Select(g => new { City = g.Key, Count = g.Count() });

이렇게 단순히 그룹별 개수를 세는 것도 코드가 좀 장황하다는 걸 느꼈을 거야. .NET 9이 나오면서 Microsoft 팀이 개발자들의 삶을 좀 더 편하게 해주려고 LINQ에 자주 쓰이는 두 가지 메서드를 추가했어:

  • CountBy — 키별로 요소 개수를 빠르게 셀 수 있는 방법이야.
  • AggregateBy — 키별로 집계할 수 있는 범용 집계기(단순히 개수만 세는 게 아니라 합계, 기타 등등도 가능).

참고로, 이 메서드들은 커뮤니티에서 요청이 많아서 추가된 거고, MoreLINQ 같은 "고급" LINQ 라이브러리에는 이미 오래전부터 있었던 기능이야.

2. CountBy 메서드: 그룹별 간결한 카운트

설명

CountBy는 말 그대로 "그룹화 후 바로 개수 세기"야. 이 메서드는 "키-개수" 쌍의 컬렉션을 반환하는데, 이건 실제 데이터 분석에서 엄청 자주 쓰여: 인기 도시, 점수별 학생 수, 어떤 게 얼마나 자주 나오는지 등등.

시그니처 (간단히):


IEnumerable<(TKey Key, int Count)> CountBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)

실전에서 써보기

예시 1: 도시별 학생 수

예를 들어, 이런 클래스가 있다고 해보자:


class Student
{
    public string Name { get; set; }
    public int Grade { get; set; }
    public string City { get; set; }
}

그리고 우리 앱에는 학생 리스트가 있어:


var students = new List<Student>
{
    new Student { Name = "이반", Grade = 5, City = "네온빌" },
    new Student { Name = "안나", Grade = 4, City = "로스-산토스" },
    new Student { Name = "이고르", Grade = 3, City = "네온빌" },
    new Student { Name = "마리아", Grade = 5, City = "로즈워터" },
    new Student { Name = "올렉", Grade = 4, City = "네온빌" }
};

.NET 8이나 그 이전에는 이렇게 써야 했지:


var group = students.GroupBy(s => s.City)
                    .Select(g => new { City = g.Key, Count = g.Count() });

foreach (var cityInfo in group)
{
    Console.WriteLine($"{cityInfo.City}: {cityInfo.Count} 학생");
}

.NET 9에서는 이걸 한 줄로 끝낼 수 있어:


var cityCounts = students.CountBy(s => s.City);
foreach (var (city, count) in cityCounts)
{
    Console.WriteLine($"{city}: {count} 학생");
}

맞아, 제대로 본 거야 — GroupBy도, Count()도 직접 쓸 필요 없어. 그냥 바로 끝!

예시 2: 점수별 학생 수

학과장이라면 꼭 궁금해할 만한 문제 — 우등생, 보통생이 몇 명인지 알고 싶을 때:


var gradeCounts = students.CountBy(s => s.Grade);

foreach (var (grade, count) in gradeCounts)
{
    Console.WriteLine($"점수 {grade}: {count} 학생");
}

예시 3: 문자열에서 문자 빈도수

CountBy는 객체뿐 아니라 더 단순한 것에도 쓸 수 있어:


string word = "supercalifragilisticexpialidocious";
var charFrequencies = word.CountBy(ch => ch);

foreach (var (letter, count) in charFrequencies)
{
    Console.WriteLine($"{letter} : {count}");
}

메모리에서 어떻게 보일까 (그림)

Count (CountBy 전) Count (CountBy 사용)
"네온빌"
3 3
"로즈워터"
1 1
"로스 산토스"
1 1

이런 방식은 코드를 읽기 쉽게 만들고, 그룹화할 때 실수도 줄여줘.

자주 하는 실수와 주의점

CountBy(Key, Count) 튜플을 반환한다는 걸 잊지 마. 익명 타입이 아니라서 약간 덜 직관적으로 보일 수도 있지만, 훨씬 명확해. 그리고 Count의 타입은 항상 int로 반환돼 — 직접 타입을 지정할 수는 없어.

3. AggregateBy 메서드: 그룹별 만능 집계

설명

CountBy가 단순 카운트라면, AggregateBy는 진짜 만능 칼이야! 판매자별 매출 합계, 반별 최고 점수, 아니면 뭐든 누적기로 표현할 수 있는 건 다 가능해. 이 모든 걸 AggregateBy가 해줄 수 있어.

시그니처 (아주 간단히):


IEnumerable<(TKey Key, TAccumulate Result)> AggregateBy<TSource, TKey, TAccumulate>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func)
  • keySelector — 어떤 기준으로 그룹화할지.
  • seed — 누적의 초기값.
  • func — 결과를 어떻게 누적할지 정의하는 함수.

실전에서 써보기

예시 1: 도시별 점수 합계

이렇게 복잡한 LINQ 대신:


var sums = students.GroupBy(s => s.City)
                   .Select(g => new { City = g.Key, Sum = g.Sum(s => s.Grade) });

이렇게 쓸 수 있어:


var sums = students.AggregateBy(
    s => s.City, // 도시별로 그룹화
    0,           // 초기 합계
    (acc, s) => acc + s.Grade // 점수 누적
);

foreach (var (city, sum) in sums)
{
    Console.WriteLine($"{city}: 점수 합계 = {sum}");
}

예시 2: 도시별 최고 점수

최대값을 구하고 싶으면 누적기만 바꿔주면 돼:


var maxByCity = students.AggregateBy(
    s => s.City,
    int.MinValue, // 초기값 — 최소값
    (max, s) => Math.Max(max, s.Grade)
);
foreach (var (city, maxGrade) in maxByCity)
{
    Console.WriteLine($"{city}: 최고 점수 = {maxGrade}");
}

예시 3: 이름을 문자열로 모으기

학생 리스트를 업데이트해보자:


var namesByCity = students.AggregateBy(
    s => s.City,
    "", // 초기 문자열
    (acc, s) => string.IsNullOrEmpty(acc) ? s.Name : acc + ", " + s.Name
);

foreach (var (city, names) in namesByCity)
{
    Console.WriteLine($"{city}: {names}");
}

내부 동작 그림 (스키마)


.   {Student, Student, Student, ...}
               |
               | (city로 그룹화)
               v
["네온빌"]  ["로즈워터"]  ["로스 산토스"]
      |           |               |
      |     (누적)                |
      |___________________________|
            |
            v
{("네온빌", 합계), ("로즈워터", 합계), ...}

"예전" 방식과 비교

예전에는 이런 작업을 하려면 GroupBy + Select 그리고 그 안에 Sum, Aggregate 같은 걸 써야 했어. 이제 AggregateBy가 이 "고통"을 숨겨주고 코드를 짧고 명확하게 만들어줘.

실습: 앱에 적용해보기

우리 학습 프로젝트 — 학생 일기장. 여기에 도시별 평균 점수 리포트를 추가해보자.


// 평균 점수는 AggregateBy로 합계와 개수를 누적해서, 나중에 평균을 계산해
var avgByCity = students.AggregateBy(
    s => s.City,
    (Sum: 0, Count: 0),
    (acc, s) => (acc.Sum + s.Grade, acc.Count + 1)
).Select(x => (x.Key, Average: (double)x.Result.Sum / x.Result.Count));

foreach (var (city, avg) in avgByCity)
{
    Console.WriteLine($"{city}: 평균 점수 = {avg:F2}");
}

여기서는 합계와 개수를 튜플로 누적하고, 마지막에 합계를 개수로 나눠서 평균을 구해.

특징과 주의할 점

누적기가 참조 타입이면, AggregateBy 내부에서 같은 참조가 그룹 내 모든 반복에 사용되니까, 객체를 변형하지 않도록 주의해야 해. 그렇지 않으면 그룹 전체 결과에 영향을 줄 수 있어.

그리고 그룹화뿐 아니라 결과를 변환하고 싶으면 바로 .Select를 써도 돼. seed (초기값)는 의미에 맞게 잘 골라야 해. 예를 들어 문자열에 0을 쓰면 안 돼.

4. 예전 방식과 새 방식 비교

문제 예전 LINQ 새 LINQ .NET 9
도시별 학생 수
GroupBy + Select + Count
students.CountBy(s => s.City)
도시별 점수 합계
GroupBy + Select + Sum
students.AggregateBy(..., 0, ...)
도시별 최고 점수
GroupBy + Select + Max
students.AggregateBy(..., ...)
문자열에서 문자 빈도수
GroupBy + Select + Count
word.CountBy(ch => ch)

실무와 실제 프로젝트에서의 장점

CountByAggregateBy 같은 메서드는 통계 리포트 작성, 데이터 시각화 준비, 피벗 테이블 만들기 등등에서 정말 없어서는 안 될 존재야. 코드가 짧아지고, 읽기 쉬워지고, 무엇보다 신뢰성이 높아져 — 그룹화, 집계 논리에서 실수할 일이 줄어드니까.

주니어/미들급 면접에서 이런 LINQ 신기능을 알고 있으면 플랫폼 발전을 잘 따라가고 있다는 인상을 줄 수 있고, 실제 서비스에서는 직접 복잡한 코드 만들지 않고, 읽기 쉽고 유지보수 쉬운 구조를 쓸 수 있어.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION