LINQ는 쿼리 언어와 그 쿼리 언어를 일련의 메서드 집합으로 변환하는 2개의 핵심 구조를 기반으로 한다. C# 컴파일러는 쿼리 언어로 작성된 쿼리 표현식을 메서드 호출 구문으로 변환해준다.
C#은 쿼리 표현식 패턴에 포함된 개별 메서드가 어떤 의미를 가지고 있는지 전혀 개의치 않는다.
예로 들어 컴파일러는 Where()과 같은 메서드가 쿼리 표현식의 패턴에서 의도하는 동작을 온전히 수행하는지를 확인하지 않으며, 구문상의 오류만을 확인할 뿐이다.
따라서 유효한 구조의 메서드라 하더라도 그 메서드가 우리가 의도하는 바를 제대로 구현하고 있는지를 컴파일러가 확인할 수 없다.
따라서 우리는 쿼리 표현식과 메서드 호출 구문이 어떻게 대응되는지 제대로 알아둘 필요가 있다.
where : Wher()
where는 필터 이상의 역할을 수행하지 않는다. Where()는 입력 시퀀스로부터 조건을 만족하는 요소만을 추려낸다.
입력과 출력 시퀀스는 동일한 타입이어야하고 입력 시퀀스의 요소를 수정하지 않아야 한다.
select : Select()
쿼리 표현식에서 select는 입력값을 다른 값으로 바꾸거나 혹은 타입을 변환하는 용도로 사용된다.
다음 쿼리는 select를 이용하여 입력값을 다른 타입으로 바꾸는 예다.
var numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var squares = from n in numbers select new { Number = n, Square = n * n };
이 예의 select 절은 쿼리 표현식 패턴 내의 Select() 메서드로 변환된다.
var squares = numbers.Select(n => new { Number = n, Square = n * n });
orderby : OrderBy(), ThenBy()
쿼리 구문 내에 정렬과 관련된 부분은 OrderBy() ThenBy(), OrderByDescsending(), ThenByedscending() 으로 변환된다.
var people = from e in employees where e.Age > 30 orderby e.LastName, e.FirstName, e.Age select e;
위 쿼리 구문은 아래와 같이 변환된다.
var people = employees.Where(e => e.Age > 30). OrderBy(e => e.LastName). OrderBy(e => e.FirstName). ThenBy(e => e.Age);
OrderBy()의 결과가 ThenBy()에 의해서 처리되고 그 결과가 다시 ThenBy()에 의해 처리되고 있다. 이와 같은 과정을 Descending을 통해서 내림차순으로 정리할 수도 있다.
group by : GroupBy()
GroupBy() 메서드는 개별 요소로 하나의 키와 값의 리스트를 쌍으로 갖는 시퀀스를 생성한다. 키는 그룹을 선택하는 셀렉터 역할을 하고 값의 리스트는 그룹 내의 항목들을 나타낸다.
var result = from d in from e in employees group e by e.Department select new { Department = d.Key, Size = d.Count()};
이 작업이 완료되면 이제 메서드 호출 구문으로 변환 작업을 수행한다.
var result = employees.GroupBy(e => e.Department). Select(d => new { Department = d.Key, Size = d.Count() });
이 쿼리 표현식 내의 select 절은 각 그룹 단위로 별도의 키와 값을 가진 시퀀스를 생성한다.
결론
만약에 커스텀 컬렉션을 사용하는 개발자들은 쿼리 구문을 사용할 때 다른 컬렉션과 완전히 동일하게 동작할 것이라고 기대한다. 실제로 IEnumerable<T> 인터페이스를 지원하면 이러한 요구 사항을 손쉽게 달성할 수 있다. 하지만 커스텀 타입의 내부적인 특성을 고려하여 더 나은 구현체를 만들기로 결정했다면 모든 형태의 쿼리 패턴에 대해 올바르게 동작하도록 구현체를 만들어야 한다.