Predicate<T>

황현중·2025년 11월 27일

C#

목록 보기
16/24

1. Predicate<T> 한 줄 정의

먼저 결론부터 보자.

Predicate<T> = T 하나를 받아서 bool을 반환하는 델리게이트 타입

C#에서 실제 정의는 이렇게 생겼다.

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

느낌상 이런 거랑 비슷하다.

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

// .NET에서 미리 만들어 둔 델리게이트
Predicate<int> test;
  • NumberTest : “int → bool” 델리게이트 타입 (사용자 정의)
  • Predicate<int> : “int → bool” 델리게이트 타입 (표준 제공)

“T가 조건을 만족하는지 검사해서 true/false를 돌려주는 함수”를 표현하기 위한 표준 타입이라고 보면 된다.


2. Func<T,bool>이랑 뭐가 다른가?

시그니처만 놓고 보면 둘은 사실 같다.

Predicate<T>   // T를 받아서 bool 반환
Func<T, bool>   // T를 받아서 bool 반환

둘 다 “T를 입력으로 받고 bool을 반환하는 함수”를 표현한다.
다만,

  • Predicate<T> : 이름 자체가 “조건 검사 함수”라는 의미를 담고 있음
  • Func<T,bool> : 그냥 “T 받아서 bool 돌려주는 일반 함수”라는 의미

요즘 C# 코드에서는 Func<T,bool>를 많이 쓰지만, List<T>, Array 같은 BCL API들은 예전부터 Predicate<T>를 사용해와서 둘 다 알아두는 게 좋다.


3. Predicate<T>가 쓰이는 대표적인 곳들

List<T>의 메서드들을 보면 Predicate<T>가 잔뜩 등장한다.

  • T Find(Predicate<T> match)
  • List<T> FindAll(Predicate<T> match)
  • int FindIndex(Predicate<T> match)
  • bool Exists(Predicate<T> match)
  • int RemoveAll(Predicate<T> match)
  • bool TrueForAll(Predicate<T> match)

숫자 리스트로 간단한 예제를 보자.

using System;
using System.Collections.Generic;

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

        // 짝수인지 판단하는 Predicate<int>
        Predicate<int> isEven = n => n % 2 == 0;

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

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

        // 3) 짝수 요소를 전부 제거하기
        int removedCount = numbers.RemoveAll(isEven);

        // 4) 짝수가 하나라도 있나?
        bool anyEven = numbers.Exists(isEven);

        Console.WriteLine("첫 짝수: " + firstEven);
        Console.WriteLine("제거된 짝수 개수: " + removedCount);
        Console.WriteLine("짝수가 하나라도 남아 있는가? " + anyEven);
    }
}

여기서 isEven의 타입이 바로 Predicate<int>다.
그리고 Find, FindAll, RemoveAll, Exists 메서드들은 내부에서 이 Predicate<int>를 호출하면서 조건을 검사한다.


4. 람다식이랑 같이 쓰는 실무 느낌 예제

실무에서는 Predicate<T> 변수를 따로 선언하기보다는, 람다식을 바로 넘겨버리는 패턴을 훨씬 많이 쓴다.

4-1. 숫자 리스트 예제

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);

        // 짝수만 모두 찾기
        List<int> evens = numbers.FindAll(n => n % 2 == 0);

        // 5보다 작은 수를 전부 지우기
        int removed = numbers.RemoveAll(n => n < 5);

        // 모든 숫자가 짝수인가?
        bool allEven = numbers.TrueForAll(n => n % 2 == 0);

        Console.WriteLine("10보다 큰 첫 숫자: " + biggerThanTen);
        Console.WriteLine("짝수 리스트: " + string.Join(", ", evens));
        Console.WriteLine("5보다 작은 수 제거 개수: " + removed);
        Console.WriteLine("모든 수가 짝수인가? " + allEven);
    }
}

여기서 n => n > 10 같은 람다식의 실제 타입은 Predicate<int>다.
즉 람다식이 “조건 함수” 역할을 하면서 List<T> 메서드 안으로 전달되고 있는 것이다.

4-2. 객체 리스트 예제 (조금 더 실무스럽게)

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 },
        };

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

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

        // 3) 모든 품목이 0보다 큰 가격인가?
        bool allPricePositive = products.TrueForAll(p => p.Price > 0);

        Console.WriteLine("C001 유효 품목 수: " + validForC001.Count);
        Console.WriteLine("삭제된 품목 제거 수: " + removedDeleted);
        Console.WriteLine("모든 품목 가격이 양수인가? " + allPricePositive);
    }
}

여기서 p => p.CustomerCode == "C001" && !p.IsDeleted 등의 람다식 타입은 모두 Predicate<Product>다.


5. 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>라고 보면 된다.


6. Predicate<T>, Func, Action 관계 정리

  • Predicate<T>
    • 시그니처: T → bool
    • 의미: “T가 조건을 만족하는지 검사하는 함수”
    • 대표 사용처: List<T>.Find, FindAll, RemoveAll, Exists, TrueForAll
  • Func<T,bool>
    • 시그니처: T → bool
    • 의미: “T를 받아서 bool을 반환하는 일반 함수”
    • 대표 사용처: LINQ의 Where, Any, All
  • Action<T>
    • 시그니처: T → void
    • 의미: “T를 받아서 뭔가 작업만 하고 결과를 반환하지 않는 함수”

한 줄로 정리하면:

Predicate<T>는 “조건 검사용 델리게이트”이고, 형태만 놓고 보면 Func<T,bool>과 동일하다.

0개의 댓글