CodeGym /행동 /C# SELF /람다식의 장단점 =>

람다식의 장단점 =>

C# SELF
레벨 49 , 레슨 3
사용 가능

1. 람다식의 장점

프로그래밍에서는 비슷한 작업이 자주 반복돼. 예를 들어: "18세 이상인 사용자만 필터링", "조건에 맞는 모든 숫자의 합을 구하기", "상품을 가격 기준으로 정렬" 같은 것들. 람다가 없으면 이런걸 위해 따로 메서드를 만들곤 했는데 — 코드에 불필요한 잡음이 생기고, 해당 동작이 한 곳에서만 쓰인다면 특히 낭비야. 람다는 코드를 더 간결하게 하고 우리가 머릿속으로 문제를 표현하는 방식에 더 가깝게 만들어줘.

람다식은 많은 현대 언어에서 표준 도구야(물론 C#만의 얘기는 아니지). 필터나 핸들러, 변환 함수처럼 "행동을 값으로 전달"할 수 있게 해주거든.

1. 간결함과 명료성

람다식을 쓰면 불필요한 긴 메서드 선언이나 익명 델리게이트를 피할 수 있어. 작은 기능을 "즉석에서" 쓸 때 특히 유용해. 예를 들어, 람다가 없던 시절의 숫자 리스트 필터링은 이렇게 보였을 거야:


List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

// 람다 등장 이전:
List<int> evenNumbers = numbers.FindAll(delegate(int x) { return x % 2 == 0; });

// 람다식으로:
List<int> evenNumbers2 = numbers.FindAll(x => x % 2 == 0);

결과는 같지만 람다 쓴 코드가 훨씬 간결하지. 큰 프로젝트에서는 줄 수 절약이 꽤 의미 있게 다가와.

2. 가독성 및 표현력 향상

람다는 연산의 핵심에 집중하게 해줘, 문법적 "소음"을 제거하니까. 코드가 자연어에 더 가깝게 보여:


var adults = users.Where(user => user.Age >= 18);

이걸 굳이 별도 메서드 bool IsAdult(User user)로 선언해서 쓰는 것과 비교해 봐 — 그 필터 하나 때문에 메서드를 하나만들 필요는 없잖아.

3. LINQ 및 컬렉션 API와의 편한 통합

람다의 힘은 LINQ와 컬렉션과 함께 쓸 때 더 커져. 표준 컬렉션과 LINQ 연산자들은 종종 함수(예: 필터용 Func<T, bool>)를 파라미터로 기대해. 람다로 그 자리에 바로 함수를 선언할 수 있어:


var expensive = products.Where(p => p.Price > 1000);
var firstBook = books.FirstOrDefault(b => b.Title.StartsWith("C#"));
var doubled = numbers.Select(n => n * 2);

4. 외부 스코프 변수 캡처(closures)

람다식은 바깥에 선언된 변수를 사용할 수 있어. 이건 유연성을 주고 즉석에서 "설정된" 함수를 생성하게 해줘:


int minAge = 18;
var filtered = users.Where(u => u.Age >= minAge); // minAge는 람다에 "캡처"됨

이걸 이용하면 "설정값"을 가진 함수를 동적으로 만들어내는 패턴을 쉽게 구현할 수 있어.

흥미로운 사실: 람다 내부에서 외부 변수를 읽는 것뿐만 아니라 경우에 따라 수정도 가능해. 다만 신중히 써야 해 — 클로저에 대한 강의에서 더 자세히 다룰게!

5. 인라인되고 컨텍스트에 맞는 코드

람다식은 사용되는 곳 가까이에 "살아" 있고, 프로젝트 전체에서 메서드를 찾아야 하지 않아. 그래서 정보가 최소 공간에 최대한 모이는 원칙에 잘 맞아.

우리 예제 애플리케이션(서점 관리 미니 시스템)을 계속 발전시킨다고 하면, 책 리스트가 이렇게 있었다고 하자:


public class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public int Year { get; set; }
    public double Price { get; set; }
}

// 코드 어딘가:
List<Book> books = new List<Book>
{
    new Book { Title = "C# 9.0 in a Nutshell", Author = "Skeet", Year = 2022, Price = 350 },
    new Book { Title = "CLR via C#", Author = "Richter", Year = 2019, Price = 250 },
    // ...
};

// 가격이 300보다 큰 모든 책 찾기:
var expensiveBooks = books.Where(b => b.Price > 300).ToList();

필터 조건을 호출부 바로 옆에서 볼 수 있으니, 코드 전체를 뒤져서 관련 함수를 찾을 필요가 없어.

6. 콜백, 이벤트, 타이머에 적합

람다는 한 번 실행되는 동작(예: 이벤트 핸들러) 정의에 딱 좋아:


button.Click += (sender, args) => Console.WriteLine("버튼이 눌렸어요!");

핸들러가 아주 단순하면 별도 메서드를 만들 필요가 없어지는 거지.

7. 델리게이트 확장

옛날에는 행동을 전달하려면 이름 있는 메서드를 선언해야 했지; 이제는 문자 그대로 그 자리에서 함수를 작성하면 돼:


Timer timer = new Timer(_ => Console.WriteLine("틱!"), null, 0, 1000);

8. 테스트와 DI(주입) 단순화

람다 덕분에 테스트용으로 가짜(mock) 행동 구현을 쉽게 만들 수 있어. 테스트용 보조 클래스나 임시 클래스로 코드가 지저분해지지 않지. 예를 들어 생성자가 델리게이트를 받는다면 테스트에서 적당한 동작을 하는 람다를 넣어주면 돼.

2. 람다식의 주요 단점

강력한 도구인 만큼 람다식도 단점이 있어. 어떤 난점과 제약이 있는지 이야기해보자.

1. 과도한 중첩 시 가독성 저하

람다는 적당할 때는 좋지만 한 곳에 너무 많으면 오히려 해가 돼. 중첩 람다나 긴 람다는 코드를 해석하기 어렵게 만들어:


var result = items.Select(x => x.Children.Where(y => y.Value > 10)
                         .Select(z => z.Name.ToUpper())
                         .ToList());

여기에 몇 단계만 더 추가하면 — 자, 이제 "코드 읽기 국민 미로" 완성.

조언: 람다가 3–4줄 이상 길어지면 별도 이름 있는 메서드로 빼라. 구식처럼 보일까 걱정하지 마 — 가독성이 패션보다 우선이야.

2. 디버깅의 어려움

람다는 디버거와 친하지 않을 때가 있어, 특히 LINQ 체인 안에 한 줄로 끼워넣었을 때. 람다 내부에 브레이크포인트를 걸거나 특정 단계의 변수값을 보기가 까다로울 수 있어.

디버깅을 쉽게 하려면 일시적으로 람다 본문을 이름 있는 메서드로 빼거나 긴 LINQ 체인을 중간 변수로 분리해라.

3. 인자 타입과 반환 타입이 불명확할 수 있음

람다는 보통 델리게이트(Func<...>, Action<...>, Predicate<T>)로 전달돼. 특히 generic을 많이 쓰는 메서드에서는 입력 파라미터 타입이나 반환 타입이 바로 와닿지 않을 때가 있어.

예를 들면:


Func<int, string, double> myFunc = (a, b) => a + b.Length; // 아이고! int가 반환될 거고, 그런데 double이어야 함.

컴파일러가 에러를 알려줄 거지만, 초보자는 "이 람다가 타입에 들어맞지 않네"를 바로 이해하기 어렵다.

4. 변수 캡처로 인한 문제

외부 변수 캡처(closure)는 양날의 검이야. 캡처한 변수를 부주의하게 쓰면 예기치 않은 결과가 나올 수 있어. 예를 들어, 루프에서:


var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
    actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions) action();

많은 사람이 출력으로 0 1 2를 기대하지만, 실제로는 3 3 3가 나온다. 왜냐하면 실행 시점에 변수 i는 이미 3이기 때문이야! 람다는 변수 자체를 "캡처"하고 그 값이 아니라 변수의 참조를 캡처하기 때문에 발생하는 현상이야.

이건 초보자에게 흔한 실수라서 공식 문서에서 자세히 다루고 있어. 해결방법은 있지만 주의가 필요해.

5. 명시적 이름 상실과 재사용 문제

람다는 한 번 쓰는 동작에 좋아. 그러나 같은 조건/함수를 여러 곳에서 쓰면 이름 있는 메서드로 로직을 빼는 게 낫다. 그렇지 않으면 중복이 생기고 수정할 때 실수할 위험이 커.

6. XML 주석 추가의 불편함

람다에는 메서드처럼 XML 문서화 주석을 붙일 수 없어. 자동 문서 생성이 필요하면 이름 있는 메서드를 써야 해. 람다에는 일반 코드 주석을 써야 한다.

7. 성능 관련 잠재적 문제

대부분 경우 람다는 성능상 눈에 띄는 손해를 주지 않아. 다만 캡처를 동반하는 람다를 자주 대량으로 생성하면 추가 객체(closure)가 할당되어 성능에 영향이 있을 수 있어. 성능이 극히 중요한 곳(예: tight loop나 고부하 서비스)에서는 static 메서드를 쓰는 편이 더 저렴할 수 있다.

8. 외부 루프에 대해 goto, break, continue 사용할 수 없음

람다가 루프 안에서 선언돼 있어도, 그 내부에서 외부 루프에 대해 직접적으로 breakcontinue를 쓰는 건 문법적으로 허용되지 않아.

9. 람다만으로 모든 행동을 표현할 수는 없음

람다는 속성(attribute)과 직접적으로 연동하거나 접근 제한자(modifier)를 지정할 수 없고, 특정 특별한 작업(예: 이름 있는 로컬 함수 선언)을 대체할 수는 없어.

3. 선택의 문제

언제 람다가 유용한가

시나리오 람다 — 편한가?
짧은 필터링/변환 👍 빠르고 명확함
다단계 중첩 연산 👎 읽기 어려워짐
Re-use (재사용) 👎 메서드로 빼는 것이 낫다
Callback 로직, 이벤트 👍 간결함
복잡한 비즈니스 로직 설명 👎 이름 + 주석이 필요함
LINQ 작업 👍 이상적인 시나리오

언제 람다를 피해야 할까

  • 로직이 길고 분기/계산이 많은 경우.
  • 람다가 보는 이에게 불명확하고 설명이 부족한 경우.
  • 함수에 문서화가 필요하거나 여러 곳에서 써야 하거나 "설명해주는" 이름이 필요한 경우.
  • 람다가 너무 깊게 중첩 호출 안에 들어가 있으면 가독성이 떨어질 위험이 있는 경우.

4. 람다식 사용 시 흔한 실수

루프에서 변수 캡처로 인한 오류:


List<Action> actions = new List<Action>();
for (int i = 0; i < 5; i++)
{
    actions.Add(() => Console.WriteLine(i));
}
foreach (var act in actions) act(); // 모두 5를 출력함!

올바른 방법:


for (int i = 0; i < 5; i++)
{
    int captured = i; // 별도 변수를 캡처
    actions.Add(() => Console.WriteLine(captured));
}

너무 긴 람다:


books.Where(b => b.Price > 1000 && b.Title.Contains("C#") && b.Author.Length > 4 && 등등...);
// 코드가 읽기 어려워짐, 메서드로 빼자!

문서화가 필요한 경우 람다 대신 메서드 사용:
함수가 여러 곳에서 쓰이거나 자세히 주석을 달아야 한다면 이름 있는 메서드를 작성하는 게 낫다:


bool IsExpensiveBook(Book book) => book.Price > 1000;
books.Where(IsExpensiveBook);
2
과제
C# SELF, 레벨 49, 레슨 3
잠금
람다를 사용한 리스트 변환
람다를 사용한 리스트 변환
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION