LINQ

황현중·2025년 11월 27일

C#

목록 보기
17/24

1. LINQ 한 줄 정의

LINQ = C# 코드 안에서 컬렉션(배열, List, DB데이터 등)을 SQL처럼 깔끔하게 필터/정렬/변환/집계하는 기능

원래는 이런 식으로 썼던 코드를,

int[] numbers = { 3, 5, 4, 2, 6, 7, 8 };
List<int> evens = new List<int>();

foreach (int n in numbers)
{
    if (n % 2 == 0)
        evens.Add(n);
}

evens.Sort();  // 오름차순 정렬

LINQ로는 이렇게 쓸 수 있다.

int[] numbers = { 3, 5, 4, 2, 6, 7, 8 };

var evens = numbers
    .Where(n => n % 2 == 0)  // 짝수만 필터
    .OrderBy(n => n)         // 오름차순 정렬
    .ToList();               // List<int>로 변환
  • Where : 조건 필터링
  • OrderBy : 정렬
  • ToList : 리스트로 변환
  • n => n % 2 == 0 : 조건을 나타내는 람다식 (Func<int,bool>)

결과는 똑같지만, LINQ 버전이 훨씬 읽기 쉽다. “무슨 일을 하는지”가 코드에 그대로 드러나는 게 장점이다.


2. LINQ는 어디에 쓸 수 있을까?

대표적으로 다음과 같은 곳에 쓸 수 있다.

  • int[], string[] 같은 배열
  • List<T>, Dictionary<K,V> 같은 컬렉션
  • DB에서 가져온 IQueryable<T> (Entity Framework 같은 ORM)
  • IEnumerable<T> 를 구현한 대부분의 타입

즉, “여러 개의 데이터가 모여 있는 것”에 대해서 거의 다 쓸 수 있다고 보면 된다.


3. 메서드 문법(Method Syntax) 기본 패턴

LINQ를 가장 많이 쓰는 방식은 메서드 체인 스타일이다.

컬렉션
    .Where(조건)
    .Select(변환)
    .OrderBy(정렬기준)
    .GroupBy(그룹기준)
    .ToList();

3-1. Where – 조건 필터링

int[] numbers = { 3, 5, 4, 2, 6, 7, 8 };

// 짝수만 가져오기
var evens = numbers
    .Where(n => n % 2 == 0)
    .ToList();
  • WhereFunc<T, bool> 타입의 함수를 받는다.
  • n => n % 2 == 0은 “n이 짝수면 true”를 반환하는 람다식이다.
  • 결과적으로 짝수만 필터링된 리스트를 얻게 된다.

3-2. Select – 모양 변환

데이터를 다른 형태로 바꾸고 싶을 때 사용한다.

int[] numbers = { 1, 2, 3, 4, 5 };

var squares = numbers
    .Select(n => n * n)  // n을 n의 제곱으로 변환
    .ToList();

문자열로 바꾸는 것도 가능하다.

var texts = numbers
    .Select(n => $"숫자: {n}")
    .ToArray();
  • Select의 인수 타입: Func<TSource, TResult>
  • 입력 타입과 출력 타입이 달라도 된다 (int → string 등)

3-3. OrderBy / OrderByDescending – 정렬

int[] numbers = { 3, 5, 4, 2, 6, 7, 8 };

var sortedAsc = numbers
    .OrderBy(n => n)         // 오름차순
    .ToList();

var sortedDesc = numbers
    .OrderByDescending(n => n) // 내림차순
    .ToList();

문자열도 똑같이 쓸 수 있다.

string[] names = { "Kim", "Park", "Lee", "Choi" };

var sortedNames = names
    .OrderBy(n => n)   // 이름 오름차순
    .ToList();

3-4. GroupBy – 그룹핑 (거래처별, 품목별 등 묶기)

실무에서 자주 쓰이는 “거래처별 매출집계”, “품목별 수량합계” 같은 작업에 사용한다.

class Order
{
    public string CustomerCode { get; set; }
    public decimal Amount { get; set; }
}

List<Order> orders = ...; // 주문 리스트라고 가정

var sumByCustomer = orders
    .GroupBy(o => o.CustomerCode)   // 거래처코드별로 그룹핑
    .Select(g => new
    {
        Customer = g.Key,           // 그룹 키 = CustomerCode
        Total    = g.Sum(o => o.Amount),
        Count    = g.Count()
    })
    .ToList();

정리하면:

  • GroupBy(o => o.CustomerCode) : CustomerCode가 같은 주문끼리 하나의 그룹이 된다.
  • g.Key : 해당 그룹의 거래처 코드
  • g.Sum(o => o.Amount) : 그 거래처의 주문금액 총합
  • g.Count() : 그 거래처의 주문 건수

4. 쿼리 문법(Query Syntax) – SQL처럼 쓰는 스타일

LINQ는 다음처럼 SQL 비슷한 문법으로도 쓸 수 있다.

int[] numbers = { 3, 5, 4, 2, 6, 7, 8 };

var evens =
    from n in numbers
    where n % 2 == 0
    orderby n
    select n;

var list = evens.ToList();

위 코드는 내부적으로는 다음과 같은 메서드 체인으로 바뀐다.

var evens = numbers
    .Where(n => n % 2 == 0)
    .OrderBy(n => n);

둘 다 똑같이 동작하니, 편한 스타일을 선택해서 쓰면 된다.
보통은 메서드 문법(점 찍고 이어 쓰는 방식)을 더 많이 사용한다.


5. LINQ와 델리게이트 / 람다 / Func / Predicate 관계

LINQ 메서드를 이해하려면, “이 메서드가 어떤 타입의 함수를 인수로 받는지”를 보면 된다.

5-1. Where의 시그니처(개념)

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate
)
  • predicate : TSource 하나를 받아서 bool을 반환하는 함수
  • 우리가 쓰는 n => n % 2 == 0 같은 람다식은 실제로 Func<int,bool> 타입이다.

5-2. List<T>.Find와 비교해 보기

public T Find(Predicate<T> match)

Predicate<T>는 사실상 Func<T,bool>와 같은 형태다(입력 T, 출력 bool).
그래서 다음 둘은 역할이 거의 같다.

  • List<T>.Find(n => n % 2 == 0)Predicate<T> 사용
  • Where(n => n % 2 == 0)Func<T,bool> 사용

결국 LINQ는 “함수를 인수로 받아서 컬렉션을 처리하는 라이브러리”이고, 그 함수는 대부분 람다식으로 작성한다.


6. 실무 느낌 예제: 주문 리스트 처리

아래는 “주문(Order) 리스트”를 LINQ로 처리하는 실무형 예제다.

using System;
using System.Collections.Generic;
using System.Linq;

namespace LambdaExample
{
    class Order
    {
        public int Id { get; set; }              // 주문번호
        public string CustomerCode { get; set; } // 거래처 코드
        public string ItemCode { get; set; }     // 품목 코드
        public DateTime OrderDate { get; set; }  // 주문일자
        public decimal Amount { get; set; }      // 주문금액
        public string Status { get; set; }       // 상태: "완료", "취소" 등
    }

    class Program
    {
        static void Main()
        {
            List<Order> orders = GetSampleOrders();

            // 1) 완료 상태이면서 50만원 이상인 주문만 조회
            var highValueOrders = orders
                .Where(o => o.Status == "완료" && o.Amount >= 500_000m)
                .OrderByDescending(o => o.Amount)
                .ToList();

            // 2) 거래처별 매출 합계 (취소 제외)
            var sumByCustomer = orders
                .Where(o => o.Status == "완료")
                .GroupBy(o => o.CustomerCode)
                .Select(g => new
                {
                    CustomerCode = g.Key,
                    TotalAmount  = g.Sum(o => o.Amount),
                    OrderCount   = g.Count()
                })
                .OrderByDescending(x => x.TotalAmount)
                .ToList();

            // 3) 공통 합계 함수 + 조건을 Func<Order,bool>로 전달
            decimal c001Total = GetTotalAmount(
                orders,
                o => o.CustomerCode == "C001" && o.Status == "완료"
            );

            decimal c002CancelTotal = GetTotalAmount(
                orders,
                o => o.CustomerCode == "C002" && o.Status == "취소"
            );
        }

        static decimal GetTotalAmount(List<Order> orders, Func<Order, bool> predicate)
        {
            return orders
                .Where(predicate)       // 조건을 람다식으로 받아서 필터
                .Sum(o => o.Amount);    // 금액 합계
        }

        static List<Order> GetSampleOrders()
        {
            return new List<Order>
            {
                new Order { Id = 1, CustomerCode = "C001", ItemCode = "ITEM-01", OrderDate = new DateTime(2025, 1, 10), Amount = 300_000m, Status = "완료" },
                new Order { Id = 2, CustomerCode = "C001", ItemCode = "ITEM-02", OrderDate = new DateTime(2025, 1, 15), Amount = 800_000m, Status = "완료" },
                new Order { Id = 3, CustomerCode = "C002", ItemCode = "ITEM-03", OrderDate = new DateTime(2025, 2, 01), Amount = 150_000m, Status = "취소" },
                new Order { Id = 4, CustomerCode = "C002", ItemCode = "ITEM-01", OrderDate = new DateTime(2025, 2, 20), Amount = 1_200_000m, Status = "완료" },
                new Order { Id = 5, CustomerCode = "C003", ItemCode = "ITEM-02", OrderDate = new DateTime(2025, 3, 05), Amount = 500_000m, Status = "완료" },
                new Order { Id = 6, CustomerCode = "C003", ItemCode = "ITEM-03", OrderDate = new DateTime(2025, 3, 10), Amount = 50_000m,  Status = "완료" },
            };
        }
    }
}

여기서 LINQ가 하는 일은 크게 세 가지다.

  1. Where로 조건에 맞는 주문만 필터링
  2. GroupBy + Select로 거래처별 집계
  3. Func<Order,bool> + Where로 공통 합계 함수에 조건을 주입

0개의 댓글