Grani Engineering Blog

株式会社グラニはC#を中心として、ASP.NET、Unity、VR開発を行っています。

C# 7.0 が使えるようになったので ValueTuple を活用してみた

アプリケーション部の田口(@t_tetsuzin)です。 社内では数少ないF#erとして潜伏中です。

待ちに待った VisualStudio2017 がリリースされましたね!
Graniではさっそく C# 7.0 を本番環境に投入しています。

そんな待望の C# 7.0 で使えるようになった新機能は

  • タプル(ValueTuple)・タプルの分解
  • Task-like
  • ローカル関数
  • 拡張されたswitch文(パターンマッチ)
  • etc…

と大きいのから小さいのまで多岐にわたりますが、 今回はタプルの便利な使い方について紹介したいと思います。

タプル以外の今回の目玉機能である Task-like はちょうど公開された弊社CTOの記事でどうぞ!

どういう風に便利なのか

※ ValueTuple は .NET Framework 4.7 未満で使用する場合は
Nuget から System.ValueTuple をパッケージをプロジェクトに追加する必要があります。

ソーシャルゲームではユーザーの情報など大量のデータを様々なところから呼び出す必要があるので、 こういう複数の非同期処理を呼ぶ場所がよく出てきます。

public async Task<int> HogeAsync()
{
    await HeavyFunctionAsync(); // 重い処理
    return 10;
}

public async Task<string> FugaAsync()
{
    
    await HeavyFunctionAsync(); // 重い処理
    return "Fuga";
}

public async Task<int[]> MogeAsync()
{
    await HeavyFunctionAsync(); // 重い処理
    return new[] { 10, 20, 30 };
}

非同期処理を並列に実行するため、今まではこのように書いていました。

public async Task Before()
{
    // C#6までの書き方
    var hogeTask = HogeAsync();
    var fugaTask = FugaAsync();
    var mogeTask = MogeAsync();

    await Task.WhenAll(hogeTask, fugaTask, mogeTask);

    var hoge = hogeTask.Result;
    var fuga = fugaTask.Result;
    var moge = mogeTask.Result;

    // hoge, fuga, moge を使った処理へつづく
    await SomeFunctionAsync(hoge, fuga, moge);
}

長いですね。とてもイケてるコードには見えません。
しかし、タプルと分解、そして拡張メソッドを組み合わせることで……

public async Task After()
{
    // C#7以降でタプル記法を使った書き方
    var (hoge, fuga, moge) = await (HogeAsync(), FugaAsync(), MogeAsync()).WhenAll();

    // hoge, fuga, moge を使った処理へつづく
    await SomeFunctionAsync(hoge, fuga, moge);
}

こんな風に書くことができるようになりました。

追加した拡張メソッド

public static class ValueTupleExtensions
{
    public static async Task<(T1, T2)> WhenAll<T1, T2>(this (Task<T1>, Task<T2>) tasks)
    {
        await Task.WhenAll(tasks.Item1, tasks.Item2).ConfigureAwait(false);
        return (tasks.Item1.Result, tasks.Item2.Result);
    }

    public static async Task<(T1, T2, T3)> WhenAll<T1, T2, T3>(this (Task<T1>, Task<T2>, Task<T3>) tasks)
    {
        await Task.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3).ConfigureAwait(false);
        return (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result);
    }

    public static async Task<(T1, T2, T3, T4)> WhenAll<T1, T2, T3, T4>(this (Task<T1>, Task<T2>, Task<T3>, Task<T4>) tasks)
    {
        await Task.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4).ConfigureAwait(false);
        return (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result, tasks.Item4.Result);
    }
}

上の例では4組のタプルまでしか挙げていませんが、8組程度なら用意してしまって良いと思います。

今回はタプルしか紹介していませんが、C# 7.0 には他にも有力な新機能が多く含まれているので、積極的に使っていきましょう! もちろん弊社のもうすぐリリースされる新タイトル黒騎士と白の魔王でも C# 7.0 が使われています!