[C#/LINQ/Tuple] LINQ에서 Named Tuple을 반환하고 싶을 때

Named Tuple?

  네임드 튜플을 알아보려면 우선 튜플부터 알아봐야겠죠. 튜플이란 아래와 같은 형식을 말합니다.
Tuple tupleA = new Tuple(1, "A", 2);
Console.WriteLine(tupleA.Item1); // 1
Console.WriteLine(tupleA.Item2); // "A"
Console.WriteLine(tupleA.Item3); // 2

  이 예제만 보고도 충분히 편리하다고 생각할 수 있지만, 더 편리하게 사용할 수 있는 방법이 있죠. 바로 튜플 리터럴(literal)을 사용하는 방법입니다.

(int, string, int) tupleB = (1, "B", 2);


  더 간단하게 해볼까요?
var tupleC = (1, "C", 2);

  충분히 간단해진 것 같습니다. 하지만 간단하다고 다 좋은 건 아니죠. 문제가 생깁니다. 바로 Item이 많아지면 Item1, Item2, ..., Item16 사이에 어떤 아이템에 어떤 값을 담았는지 확인하기가 어려워집니다. 아래와 같은 상황이 있다고 생각해 봅시다.

var tupleComplex = (1, 2, 3, "Complex", new List() { 1, 2, 3 }, new byte[] { 0x32, 0x33, 0x34, 0x00 }, 0.82);

  Item1, Item2, Item3은 각각 클라이언트의 고유번호, 연결된 다른 클라이언트의 고유번호, 연결된 게시판의 고유번호이고, Item4는 클라이언트의 이름, Item5는 클라이언트의 즐겨찾는 게시판 고유번호, Item6는 인증키, Item7은 클라이언트의 현재 목표 달성 퍼센테이지라고 가정해 봅시다. 여러분은 코딩 중에 저 것들을 다 외워서 하실건가요?

  물론, 굳이 일부러 외우지 않아도 사용할 때마다 코드에 들어가서 이건 이거고, 저건 저거고... 하면서 자연스레 외워질 수도 있겠습니다만, 너무 비효율적이죠. 그래서 있는 기능이 바로 네임드 튜플! 아래 예시를 보시죠.

var namedTuple = (
 clientId: 1,
 relatedClientId: 2,
 relatedBoardId: 3,
 clientName: "Named",
 favoriteBoardIdList: new List { 1, 2, 3 },
 authKey: new byte[] { 0x32, 0x33, 0x34, 0x00 },
 expRatio: 0.82
)

  그냥 딱 보시면 감이 오시죠? 마치 Lua의 테이블마냥, 이제 namedTuple에 접근할 때 필드명을 사용해 접근할 수 있게 됩니다! 바로 아래와 같이요.

void WL(object line) { Console.WriteLine(line.ToString()); }
WL(namedTuple.clientId); // 1
WL(namedTuple.relatedClientId); // 2
WL(namedTuple.relatedBoardId); // 3
WL(namedTuple.clientName); // "Named"
// ...


  보신 바와 같이, 네임드 튜플은 여러 형식의 데이터를 반환받거나, 저장해 사용해야 할 때 빛을 발합니다.

무엇이 문제인가? 튜플 리터럴 사용

  하지만 신세계를 이제 막 접한 저에게 갑자기 들이닥친 문제가 있었으니, 바로 LINQ에서 NamedTuple을 어떻게 반환해야 하는가였습니다. 처음 짠 코드는 아래와 같습니다.

var resultList = (from word in wordList
    //...
    select new (value: grouped.Key, posList: grouped.Select(pair => pair.Value).ToList(), count: grouped.Count())).ToList();


  코드가 무엇을 의미하는지 모르시겠다면, 당연한 현상입니다. 이 포스트에서 설명에 필요한 부분을 제외하고는 다 잘라내고 가공했으니까요. 아무튼, select new 뒷부분을 봐 주세요.
  제가 처음 의도한 바는, 저 코드가 들어있는 메소드의 반환 형식인 (string value, List posData, int count) 형식에 맞는 값을 LINQ에서 바로 반환받는 것이었습니다. 하지만, 저렇게 코딩을 하니 아래와 같은 컴파일 에러가 발생하더라고요.

'new'는 튜플 형식과 함께 사용할 수 없습니다. 대신 튜플 리터럴 식을 사용하세요.
'new' cannot be used value type tuple. Use a tuple literal expression instead.


  그래서 select new Tuple<string value, List<int> posData, int count>(...)과 같이 바꿨다가, 정답을 제외한 거의 모든 방법을 시도하고 결국 아주 기본적인 것으로 돌아가 솔루션을 찾았죠.

select (value: grouped.Key, posList: grouped.Select(pair => pair.Value).ToList(), count: grouped.Count())


  결국 이번에도 에러 메세지를 제대로 읽지 않은 대가로 몸이 고생했습니다. 튜플 리터럴 식이란 말 그대로, new가 필요 없는 리터럴 식 그 자체였습니다. select 이후 클래스의 새 인스턴스를 반환하던 습관이 저를 나락의 구렁텅이로 빠뜨려버린 것이죠. 튜플을 반환할 때, 당연하게도, 변수 선언에 튜플을 대입할 때와 같이, new는 필요 없습니다. 그냥 튜플 리터럴 식을 사용하시면 됩니다.

참고자료: https://docs.microsoft.com/en-us/dotnet/csharp/tuples

댓글

이 블로그의 인기 게시물

C# 남아도는 메모리에도 불구하고 OutOfMemoryException이 발생한다면?

MySQL 데이터 타입과 Java 데이터 타입 비교/매칭

테일즈위버 OST 전곡 모음