Programming/C# & Unity

C# Tuple, ValueTuple

장형이 2021. 9. 3. 18:03

1. Tuple

private Tuple<EErrorCode, CQuest, CReward> CompleteQuest(Int32 iID_)
{
    return Tuple.Create<EErrorCode, CQuest, CReward>(EErrorCode.INVALID_ID, null, null);
}

public void Main()
{
    var tuResult = CompleteQuest(5);
    if(tuResult.Item1 != EErrorCode.OK)
    {
        Logging($"Error : {tuResult.Item1}.");
        return;
    }

    UpdateQuest(tuResult.Item2);
    ReceiveReward(tuResult.Item3);
}

Tuple은 C++의 그것과 동일하다.

여러 가지를 한 번에 전달해야 하는데 구조체 같은 것으로 감쌀 만큼 재사용성이나 스펙이 크지 않을 때 사용한다.

 

2. Tuple 단점

// Rank Score 각각 보상을 받았는지?
private Tuple<bool, bool> IsReceivable()
{
    bool bRankReward = false;
    bool bScoreReward = false;

    return Tuple.Create(bRankReward, bScoreReward);
}

private Tuple<Int64, Int32, Int64> GetRankData(Int64 biUID_)
{
    Int32 iRank = RedisManager.GetRank(biUID_);
    Int64 biScore = RedisManager.GetScore(biUID_);

    return Tuple.Create<Int64, Int32, Int64>(biUID_, iRank, biScore);
}

public void ReceivePVPReward()
{
    var tuReceivable = IsReceivable();
    if(tuReceivable.Item1 & tuReceivable.Item2 == false)
    {
        return;
    }

    var tuRankData = GetRankData(biMyUID);
    // Rank 보상을 받는다.
    if (tuReceivable.Item1 == true)
    {
        ReceiveRankReward(tuRankData.Item2);
    }

    // Score 보상을 받는다.
    if (tuReceivable.Item2 == true)
    {
        ReceiveScoreReward(tuRankData.Item3);
    }
}

같은 타입을 리턴했을때 구분하는 게 거의 불가능하고, // Tuple<bool, bool> 쓰면 지옥 시작.

Tuple을 받은 측에서는 코드를 파악할 때 난감한 문제가 있다. // Item1 는 뭐고 Item 2는 뭐지,,?

ValueTuple를 쓰면 위 문제가 꽤나 만족스럽게 해결된다.

 

3. ValueTuple

// Rank Score 각각 보상을 받았는지?
private (bool bRankReward, bool bScoreReward) IsReceivable()
{
    bool bRankReward = false;
    bool bScoreReward = false;

    return (bRankReward, bScoreReward);
}

private (Int64 biUID, Int32 iRank, Int64 biScore) GetRankData(Int64 biUID_)
{
    Int32 iRank = RedisManager.GetRank(biUID_);
    Int64 biScore = RedisManager.GetScore(biUID_);

    return (biUID_, iRank, biScore);
}

public void ReceivePVPReward()
{
    var tuReceivable = IsReceivable();
    if(tuReceivable.bRankReward & tuReceivable.bScoreReward == false)
    {
        return;
    }

    var tuRankData = GetRankData(biMyUID);
    // Rank 보상을 받는다.
    if (tuReceivable.bRankReward == true)
    {
        ReceiveRankReward(tuRankData.iRank);
    }

    // Score 보상을 받는다.
    if (tuReceivable.bScoreReward == true)
    {
        ReceiveScoreReward(tuRankData.biScore);
    }
}

Tuple<bool, bool>.  ->     (bool bName1, bool bName2) 

tuResult.item1          ->     tuResult.bName1

 

ValueTuple로 바꾸면 가독성이 훨씬 깔끔해진다.

 

추가적으로 ValueTuple은 Tuple과 달리 안의 값을 수정이 가능하고 값 타입으로 저장하며,

Null을 넣을 수가 없어서 실패 체크를 따로 Flag 같은 것으로 해줘야 한다. (Default 키워드로 어느정도 때울 수는 있는데 비추.)

 

ValueTuple은 C# 7.0 (.Net 4.7 이상)에서 정식 지원하고, 하위 버전에서는 NuGet으로 받아서 사용이 가능하다.

(ValueTuple Default는 C# 7.3부터 가능함.)

 

더 깊게

Tuple은 값 타입, ValueTuple은 참조 타입이다.

TupleType은 ValueTuple은 것을 지칭한다고 볼 수 있으며, (int, int)와 같이 선언하면 내부적으로 ValueTuple<int,int>로 변환된다고 한다.