List<T>에서 Predicate<T>

황현중·2025년 11월 27일

C#

목록 보기
15/24

1. Predicate<T> 간단 복습

먼저 Predicate<T>가 뭔지 한 줄로 다시 짚고 가자.

Predicate<T> = T 하나를 받아서 bool을 반환하는 “조건 함수” 델리게이트 타입

C# 정의는 아래처럼 생겼다.

public delegate bool Predicate<in T>(T obj);

예를 들어,

// 내가 직접 만든 델리게이트
delegate bool NumberTest(int n);

// .NET이 제공하는 표준 델리게이트
Predicate<int> test;

둘 다 “int를 입력으로 받아 bool을 반환하는 함수”를 표현하는 타입이다.
차이는 하나는 내가 만든 이름이고, 하나는 .NET에서 미리 만들어 둔 표준 이름이라는 것뿐.


2. List<T>에서 Predicate<T>를 받는 대표 메서드들

List<T> 내부에는 아래처럼 Predicate<T>를 받는 메서드들이 많다.

  • T Find(Predicate<T> match) – 조건에 맞는 첫 번째 요소
  • T FindLast(Predicate<T> match) – 조건에 맞는 마지막 요소
  • List<T> FindAll(Predicate<T> match) – 조건에 맞는 모든 요소
  • int FindIndex(Predicate<T> match) – 조건에 맞는 첫 요소의 인덱스
  • int FindLastIndex(Predicate<T> match) – 조건에 맞는 마지막 요소의 인덱스
  • bool Exists(Predicate<T> match) – 조건을 만족하는 요소가 하나라도 있는지
  • int RemoveAll(Predicate<T> match) – 조건을 만족하는 요소 모두 삭제
  • bool TrueForAll(Predicate<T> match) – 모든 요소가 조건을 만족하는지

공통점은 전부 “조건을 넘겨서 그에 맞는 요소들을 찾거나, 지우거나, 검사하는 메서드”라는 것. 그래서 파라미터 타입이 전부 Predicate<T>다.


3. int 리스트에서 Predicate<int> 사용 예제

3-1. Predicate<int> 변수를 만들어서 쓰는 패턴

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<int> numbers = new List<int> { 3, 5, 4, 2, 6, 7, 8 };

        // 1) Predicate<int> 변수 선언: 짝수이면 true
        Predicate<int> isEven = n => n % 2 == 0;

        // 2) 첫 번째 짝수 찾기
        int firstEven = numbers.Find(isEven);      // 4

        // 3) 모든 짝수 찾기
        List<int> evenList = numbers.FindAll(isEven);  // { 4, 2, 6, 8 }

        // 4) 짝수가 하나라도 있는지 검사
        bool anyEven = numbers.Exists(isEven);     // true

        // 5) 짝수 전부 삭제
        int removedCount = numbers.RemoveAll(isEven); // 짝수 개수만큼 삭제

        // 6) 남아 있는 모든 숫자가 짝수인가?
        bool allEven = numbers.TrueForAll(isEven);

        Console.WriteLine("첫 짝수: " + firstEven);
        Console.WriteLine("짝수 개수: " + evenList.Count);
        Console.WriteLine("짝수 존재 여부: " + anyEven);
        Console.WriteLine("지워진 짝수 개수: " + removedCount);
        Console.WriteLine("남은 값이 모두 짝수인가? " + allEven);
    }
}

여기서 핵심은 이 한 줄이다.

Predicate<int> isEven = n => n % 2 == 0;
  • n → 리스트의 각 요소
  • n % 2 == 0 → 짝수면 true, 아니면 false
  • 즉, “짝수인지 검사하는 조건 함수”를 Predicate<int> 타입으로 담은 것

이후 Find, FindAll, Exists, RemoveAll, TrueForAll은 내부에서 이 isEven을 계속 호출하면서 조건을 체크한다.


4. 실무에서 더 많이 쓰는 패턴: 람다식을 바로 넘기기

실제 코드에서는 Predicate<T> 변수를 굳이 선언하지 않고, 아래처럼 람다식을 바로 넘기는 경우가 훨씬 많다.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<int> numbers = new List<int> { 3, 5, 4, 2, 6, 7, 8, 11, 13, 15, 20 };

        // 10보다 큰 첫 번째 숫자 찾기
        int biggerThanTen = numbers.Find(n => n > 10);

        // 5보다 작은 숫자들 모두 찾기
        List<int> lessThanFive = numbers.FindAll(n => n < 5);

        // 3의 배수 전부 제거
        int removedMultiplesOf3 = numbers.RemoveAll(n => n % 3 == 0);

        // 모든 원소가 양수인지 검사
        bool allPositive = numbers.TrueForAll(n => n > 0);

        Console.WriteLine("10보다 큰 첫 숫자: " + biggerThanTen);
        Console.WriteLine("5보다 작은 수들: " + string.Join(", ", lessThanFive));
        Console.WriteLine("3의 배수 제거 개수: " + removedMultiplesOf3);
        Console.WriteLine("모든 수가 양수인가? " + allPositive);
    }
}

위 코드에서 n => n > 10, n => n < 5, n => n % 3 == 0 같은 람다식의 실제 타입이 전부 Predicate<int>다.

문법상으로는 람다식만 보이지만, Find, FindAll, RemoveAll, TrueForAll의 시그니처를 보면 전부 Predicate<int> match를 받고 있다는 점을 기억해 두면 이해가 쉽다.


5. 객체 리스트에서 Predicate<T> 사용 (실무 스타일)

실무에서는 int 리스트보다는 보통 객체 리스트에서 조건 검색을 많이 한다. 예를 들어 ERP에서 “품목 리스트”, “출고 리스트” 같은 걸 다룰 때도 이 패턴을 그대로 적용할 수 있다.

using System;
using System.Collections.Generic;

class Product
{
    public string ItemCode { get; set; }
    public string CustomerCode { get; set; }
    public decimal Price { get; set; }
    public bool IsDeleted { get; set; }
}

class Program
{
    static void Main()
    {
        List<Product> products = new List<Product>
        {
            new Product { ItemCode = "A-100", CustomerCode = "C001", Price = 1000, IsDeleted = false },
            new Product { ItemCode = "A-200", CustomerCode = "C001", Price = 5000, IsDeleted = true  },
            new Product { ItemCode = "A-300", CustomerCode = "C002", Price = 7000, IsDeleted = false },
            new Product { ItemCode = "B-100", CustomerCode = "C002", Price = 1500, IsDeleted = false },
        };

        // 1) C001 거래처의 유효(삭제 안 된) 품목만 찾기
        List<Product> validForC001 = products.FindAll(p =>
            p.CustomerCode == "C001" &&
            !p.IsDeleted
        );

        // 2) 삭제된 품목 전부 제거
        int removedDeleted = products.RemoveAll(p => p.IsDeleted);

        // 3) 가격이 0 이하인 상품이 하나라도 있는지 검사
        bool hasInvalidPrice = products.Exists(p => p.Price <= 0);

        // 4) 모든 품목이 특정 거래처(C002)인지 검사
        bool allForC002 = products.TrueForAll(p => p.CustomerCode == "C002");

        Console.WriteLine("C001 유효 품목 수: " + validForC001.Count);
        Console.WriteLine("삭제된 품목 제거 개수: " + removedDeleted);
        Console.WriteLine("0 이하 가격 품목 존재 여부: " + hasInvalidPrice);
        Console.WriteLine("모든 품목이 C002인가? " + allForC002);
    }
}

여기서:

  • p => p.CustomerCode == "C001" && !p.IsDeletedPredicate<Product>
  • p => p.IsDeleted → 삭제 조건, RemoveAll에서 사용
  • p => p.Price <= 0 → 가격 유효성 검사 조건
  • p => p.CustomerCode == "C002"TrueForAll에서 전체 검사

이런 패턴은 “출고 데이터 중 특정 조건만 찾기 / 특정 상태 데이터만 삭제하기 / 전체 유효성 검사” 등에 그대로 응용할 수 있다.


6. Predicate<T> 타입을 직접 써보면 감이 더 잘 온다

람다를 바로 넘겨도 되지만, 처음에는 타입을 명시적으로 써보면 더 이해가 잘 된다.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Predicate<int>를 명시적으로 선언
        Predicate<int> isEven = n => n % 2 == 0;

        // 직접 호출도 가능
        bool r1 = isEven(4);  // true
        bool r2 = isEven(5);  // false

        Console.WriteLine(r1);
        Console.WriteLine(r2);

        // List<int>.Find에 그대로 넘기기
        List<int> list = new List<int> { 3, 4, 5, 6 };
        int firstEven = list.Find(isEven);   // 4

        Console.WriteLine("첫 짝수: " + firstEven);
    }
}

사실 이 코드는 아래와 완전히 같은 의미다.

Func<int, bool> isEven2 = n => n % 2 == 0;
bool r3 = isEven2(4);

차이는 이름 뿐이다. Predicate<T>는 “조건 검사 함수”라는 의미가 더 분명하고, Func<T,bool>는 더 일반적인 함수 타입이라는 정도.


0개의 댓글