Computer >> 컴퓨터 >  >> 프로그래밍 >> C#

C# LINQ에서 'NOT IN' 쿼리를 구현하는 방법: Except 연산자 완벽 가이드

SQL에 익숙한 개발자라면 NOT IN 절을 사용해 한 집합에는 있지만 다른 집합에는 없는 데이터를 조회하는 방법을 잘 알고 있을 것입니다. 그렇다면 C# LINQ에서는 이와 같은 기능을 어떻게 구현할 수 있을까요? 정답은 바로 Except 연산자입니다.

Except 연산자란 무엇인가?

Except 연산자는 IEnumerable<T> 인터페이스를 지원하는 데이터 소스를 대상으로 동작하도록 설계된 집합 연산자(Set Operator)입니다. 모든 LINQ 쿼리 식과 대부분의 LINQ 쿼리는 IEnumerable<T>를 반환하기 때문에, 이 연산자를 활용하면 LINQ 쿼리의 결과물에 대해 손쉽게 집합 연산을 수행할 수 있습니다.

Except 연산자의 동작 방식은 직관적입니다. 첫 번째 목록에는 존재하지만 두 번째 목록에는 없는 항목들만 반환합니다. 즉, SQL의 NOT IN 절과 동일한 역할을 수행하는 것입니다.

예제 1: Except 메서드 사용하기

가장 기본적인 사용법은 메서드 구문(Method Syntax)에서 Except를 호출하는 것입니다.

class Program{
    static void Main(string[] args){
        var listA = Enumerable.Range(1, 6);
        var listB = new List<int> { 3, 4 };
        var listC = listA.Except(listB);
        foreach (var item in listC){
            Console.WriteLine(item);
        }
        Console.ReadLine();
    }
}

위 예제에서는 두 개의 리스트를 준비했습니다. 리스트 A는 1부터 6까지의 숫자를 포함하고, 리스트 B는 3과 4를 포함합니다. 그런 다음 Except 연산자를 사용해 리스트 A 중에서 리스트 B에 존재하지 않는 요소만 추출합니다.

실행 결과

1
2
5
6

실행 결과를 보면 리스트 B에 포함된 3과 4가 제외되고 나머지 값만 출력된 것을 확인할 수 있습니다.

예제 2: SQL 스타일 쿼리 구문 사용하기

메서드 구문 대신 SQL과 유사한 쿼리 식(Query Syntax)을 선호한다면, Any 메서드와 논리 부정 연산자(!)를 조합하여 동일한 결과를 얻을 수 있습니다.

static void Main(string[] args){
    var listA = Enumerable.Range(1, 6);
    var listB = new List<int> { 3, 4 };
    var listC = from c in listA
                where !listB.Any(o => o == c)
                select c;
    foreach (var item in listC){
        Console.WriteLine(item);
    }
    Console.ReadLine();
}

실행 결과

1
2
5
6

두 방식 모두 동일한 결과를 출력하지만, 내부적으로 살펴보면 차이가 있습니다.

어떤 방식을 선택해야 할까?

  • Except 연산자: 코드가 간결하고 의도가 명확하게 드러납니다. 내부적으로 해시 집합(HashSet)을 활용해 효율적으로 처리되므로 성능 면에서도 유리합니다.
  • Any + 부정 조건: 복잡한 조건이나 커스텀 비교 로직이 필요할 때 더 유연하게 확장할 수 있습니다. 다만 요소 수가 많아질 경우 Except보다 성능이 떨어질 수 있습니다.

또한 참고할 점은, Except 연산자는 기본적으로 기본 형식(primitive type)의 같음 비교자(Default Equality Comparer)를 사용한다는 것입니다. 사용자 정의 클래스 객체를 비교해야 한다면 IEqualityComparer<T>를 구현하여 오버로드된 Except 메서드에 전달해야 올바른 결과를 얻을 수 있습니다.

마무리

C# LINQ에서 SQL의 NOT IN과 같은 기능이 필요하다면 Except 연산자가 가장 깔끔하고 효율적인 해답입니다. 간단한 숫자 목록부터 복잡한 객체 컬렉션까지, 적절한 비교자만 준비하면 두 데이터 집합 간의 차집합 연산을 손쉽게 처리할 수 있습니다.