LINQ 쿼리 작업의 형식 관계(C#)

쿼리를 효과적으로 작성하려면 전체 쿼리 작업의 변수 형식이 모두 어떻게 서로 관련되는지를 이해해야 합니다. 이러한 관계를 이해하면 설명서의 LINQ 샘플 및 코드 예제를 더 쉽게 이해할 수 있습니다. 또한 var을 사용하면 변수를 암시적으로 입력할 때 발생하는 상황을 이해할 수 있습니다.

LINQ 쿼리 작업은 데이터 소스, 쿼리 자체 및 쿼리 실행에서 강력하게 형식화됩니다. 쿼리의 변수 형식은 데이터 소스의 요소 형식 및 foreach 문의 반복 변수 형식과 호환되어야 합니다. 이 강력한 형식화는 사용자가 발견하기 전에 수정될 수 있도록 컴파일 시간에 형식 오류가 catch되도록 합니다.

이러한 형식 관계를 보여 주기 위해 뒤에 나오는 대부분의 예제에서는 모든 변수에 명시적 형식화를 사용합니다. 마지막 예에서는 var을 통해 암시적 형식화를 사용하는 경우에도 어떻게 동일한 원칙이 적용되는지를 보여 줍니다.

소스 데이터를 변환하지 않는 쿼리

다음 그림에서는 데이터 변환을 수행하지 않는 LINQ to Objects 쿼리 작업을 보여 줍니다. 소스에는 문자열 시퀀스가 포함되어 있고, 쿼리 출력도 문자열 시퀀스입니다.

Diagram that shows the relation of data types in a LINQ query.

  1. 데이터 소스의 형식 인수에 따라 범위 변수의 형식이 결정됩니다.
  2. 선택된 개체의 형식에 따라 쿼리 변수의 형식이 결정됩니다. 여기서 name은 문자열입니다. 따라서 쿼리 변수는 IEnumerable<string>입니다.
  3. 쿼리 변수는 foreach 문에서 반복됩니다. 쿼리 변수가 문자열 시퀀스이기 때문에 반복 변수도 문자열입니다.

소스 데이터를 변환하는 쿼리

다음 그림에서는 데이터에 대한 간단한 변환을 수행하는 LINQ to SQL 쿼리 작업을 보여 줍니다. 쿼리는 Customer 개체 시퀀스를 입력으로 사용하고 결과에서 Name 속성만 선택합니다. Name이 문자열이기 때문에 쿼리에서 출력으로 문자열 시퀀스를 생성합니다.

Diagram showing a query that transforms the data type.

  1. 데이터 소스의 형식 인수에 따라 범위 변수의 형식이 결정됩니다.
  2. select 문은 전체 Customer 개체가 아니라 Name 속성을 반환합니다. Name이 문자열이므로 custNameQuery의 형식 인수는 Customer가 아니라 string입니다.
  3. custNameQuery가 문자열 시퀀스이므로 foreach 루프의 반복 변수도 string이어야 합니다.

다음 그림에서는 약간 더 복잡한 변환을 보여 줍니다. select 문은 원래 Customer 개체의 두 멤버만 캡처하는 무명 형식을 반환합니다.

Diagram showing a more complex query that transforms the data type.

  1. 데이터 소스의 형식 인수는 항상 쿼리의 범위 변수 형식입니다.
  2. select 문이 무명 형식을 생성하기 때문에 var을 사용하여 쿼리 변수를 암시적으로 형식화해야 합니다.
  3. 쿼리 변수의 형식이 암시적이기 때문에 foreach 루프의 반복 변수도 암시적이어야 합니다.

컴파일러에서 형식 정보를 유추하도록 허용

쿼리 작업의 형식 관계를 이해해야 하지만 컴파일러가 모든 작업을 대신 수행하도록 하는 옵션도 있습니다. var 키워드를 쿼리 작업의 모든 지역 변수에 사용할 수 있습니다. 다음 그림은 앞에서 설명한 예제 번호 2와 유사합니다. 그러나 컴파일러는 쿼리 작업의 각 변수에 대해 강력한 형식을 제공합니다.

Diagram that shows the type flow with implicit typing.

LINQ 및 제네릭 형식(C#)

LINQ 쿼리는 제네릭 형식을 기반으로 합니다. 제네릭에 대한 세부 지식이 없어도 쿼리 작성을 시작할 수 있습니다. 그러나 다음 두 가지 기본 개념은 이해하는 것이 좋습니다.

  1. List<T> 같은 제네릭 컬렉션 클래스의 인스턴스를 만들 때 “T”를 목록에 포함할 개체 형식으로 대체합니다. 예를 들어 문자열 목록은 List<string>으로 표현되고, Customer 개체 목록은 List<Customer>로 표현됩니다. 제네릭 목록은 강력한 형식이어야 하며 해당 요소를 Object로 저장하는 컬렉션에 비해 많은 장점을 제공합니다. List<string>Customer를 추가하려고 하면 컴파일 시간에 오류가 발생합니다. 런타임 형식 캐스팅을 수행할 필요가 없기 때문에 제네릭 컬렉션을 사용하기가 쉽습니다.
  2. IEnumerable<T>foreach 문을 사용하여 제네릭 컬렉션 클래스를 열거할 수 있는 인터페이스입니다. 제네릭 컬렉션 클래스는 ArrayList 등의 제네릭이 아닌 컬렉션이 IEnumerable을 지원하는 것처럼 IEnumerable<T>을 지원합니다.

제네릭에 대한 자세한 내용은 제네릭을 참조하세요.

LINQ 쿼리의 IEnumerable<T> 변수

LINQ 쿼리 변수는 IEnumerable<T> 또는 파생 형식(예: IQueryable<T>)으로 형식화됩니다. 형식이 IEnumerable<Customer>인 쿼리 변수가 표시되면 쿼리가 실행될 때 0개 이상의 Customer 개체 시퀀스를 생성한다는 의미입니다.

IEnumerable<Customer> customerQuery =
    from cust in customers
    where cust.City == "London"
    select cust;

foreach (Customer customer in customerQuery)
{
    Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

컴파일러에서 제네릭 형식 선언을 처리하도록 허용

원하는 경우 var 키워드를 사용하여 제네릭 구문을 방지할 수 있습니다. var 키워드는 from 절에 지정된 데이터 소스를 확인하여 쿼리 변수의 형식을 유추하도록 컴파일러에 지시합니다. 다음 예제에서는 이전 예제와 동일하게 컴파일된 코드를 생성합니다.

var customerQuery2 =
    from cust in customers
    where cust.City == "London"
    select cust;

foreach(var customer in customerQuery2)
{
    Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

var 키워드는 변수의 형식이 명확하거나 그룹 쿼리에 의해 생성되는 형식과 같이 중첩된 제네릭 형식을 명시적으로 지정하는 것이 중요하지 않은 경우에 유용합니다. 일반적으로 var을 사용하는 경우 다른 사용자가 코드를 읽기가 더 어려워질 수 있습니다. 자세한 내용은 암시적으로 형식화된 지역 변수를 참조하세요.