첫 번째 예는 튜플 리터럴 내에서 지정한 요소의 이름이 튜플 타입에 어떻게 영향을 미치는지 보여준다. 마지막 예는 복잡한 표현식도 타입 추론이 올바르게 수행됨을 보여주는데, input의 타입은 람다 표현식 내에 있는 x를 string 타입으로 고정하고, x.Length가 string 타입 내의 Length로 바인딩되도록 해준다. 결국 튜플 요소의 타입은 각각 string과 int가 되며, 람다 표현식의 타입은 (string, int)로 추론된다. 예제 11-7에서 다루었던 시퀀스 생성기 또한 비슷한 형태의 추론을 수행하고 있었지만, 앞에서는 타입에 집중하지 않았기 때문에 그 내용을 알기는 어려웠을 것이다.
타입을 가지는 튜플 리터럴은 이 정도로 충분한 것 같다. 그렇다면 타입을 가지지 않는 튜플 리터럴로는 무엇을 할 수 있을까? 이름이 없는 튜플 리터럴을 이름이 있는 튜플 리터럴로 어떻게 변환할 수 있을까? 이러한 질문에 답하려면 튜플 변환을 살펴봐야 한다.
튜플 변환에서는 두 가지 변환, 즉 튜플 리터럴을 튜플 타입으로 변환하는 것과 특정 튜플 타입을 다른 튜플 타입으로 변환하는 것을 살펴봐야 한다. 8장에서 이러한 종류의 차이점을 살펴본 바 있는데, 보간 문자열 리터럴 표현식을 FormattableString으로 변환할 수는 있었지만 string 타입을 FormattableString 타입으로 변환할 수는 없었다. 동일한 방식이 여기서도 그대로 적용된다. 우선 튜플 리터럴 변환을 살펴보자.
» 람다 표현식의 매개변수는 튜플처럼 보일 수 있다
하나의 매개변수를 가지는 람다 표현식은 어려울 것이 없지만, 두 개 이상의 매개변수를 가지는 람다 표현식은 흡사 튜플처럼 보일 수 있다. 예를 들어 LINQ의 select 쿼리를 이용하여 인덱스와 값을 개별 요소로 함께 가지는 시퀀스를 생성해 보자. 다른 연산을 수행할 때 인덱스를 함께 전달하면 종종 유용할 수 있기 때문에 인덱스와 값을 하나의 튜플에 저장하면 편리하다. 이를 다음과 같이 구현할 수 있다.
static IEnumerable<(T value, int index)> WithIndex<T>
(this IEnumerable<T> source) =>
source.Select((value, index) => (value, index));
람다 표현식에 좀 더 집중해 보자.
(value, index) => (value, index)
이 코드에서 첫 번째로 나타나는 (value, index) 부분은 튜플 리터럴이 아니다. 이는 람다 표현식의 매개변수의 목록이다. 하지만 두 번째 나타나는 (value, index) 부분은 람다 표현식의 반환값인 튜플 리터럴이다.
이 코드에 무슨 문제가 있는 것은 아니다. 다만, 이와 유사한 코드를 봤을 때 놀라지 않길 바랄 따름이다.