CS801~2000

C# コンパイラエラー CS1997 について解説:async メソッドで発生する return 文エラーの対処法

CS1997 は、async 修飾子付きのメソッドが Task型を返す際に、return キーワードで具体的なオブジェクトを指定した場合に発生するコンパイラエラーです。

非ジェネリック Task を返す実装では、return文で値を戻す必要がなく、await の結果をそのまま利用するか、async や await を削除して返却方法を見直す必要があります。

非同期処理の基本構造

async 修飾子と await の動作

C# の非同期処理では、メソッドに async 修飾子を付与することで、内部で await キーワードを使用して非同期処理の待機が可能になります。

async 修飾子の効果として、メソッドの実行が状態機械に変換され、指定した待機可能ステートメント(例:await Task.Delay(1000))の完了まで呼び出し元に制御を返す構造になります。

この仕組みによって、重い処理をブロッキングせずに、効率的に複数の処理を並行して実行できるようになります。

以下は、asyncawait を利用した非同期メソッドのサンプルコードです。

このコードでは、1 秒間の待機処理を行い、待機終了後に画面へメッセージを表示します。

using System;
using System.Threading.Tasks;
class Program
{
    // 非同期処理を実行するメソッド
    public static async Task PerformAsyncOperation()
    {
        // 1秒待機する処理
        await Task.Delay(1000);
        Console.WriteLine("非同期処理完了");
    }
    static async Task Main(string[] args)
    {
        // 非同期メソッドの呼び出し
        await PerformAsyncOperation();
    }
}
非同期処理完了

Task と Task<T> の使い分け

非同期メソッドの戻り値には、TaskTask<T> の二種類が利用されます。

  • Task は、戻り値を持たない非同期処理を表現する場合に使用します。
  • Task<T> は、非同期処理の結果として値を返す場合に使用します。

この 2 種類の使い分けにより、メソッドの目的に応じた型安全な戻り値が実現できるという利点があります。

以下に、TaskTask<T> を利用するサンプルコードを示します。

using System;
using System.Threading.Tasks;
class Program
{
    // 戻り値を持たない非同期処理メソッド
    public static async Task ExecuteOperation()
    {
        await Task.Delay(500);  // 500ミリ秒待機
        Console.WriteLine("結果を返さない非同期処理完了");
    }
    // 戻り値を持つ非同期処理メソッド
    public static async Task<int> CalculateResult()
    {
        await Task.Delay(500);  // 500ミリ秒待機
        return 42;  // 計算結果を返す
    }
    static async Task Main(string[] args)
    {
        await ExecuteOperation();
        int result = await CalculateResult();  // 非同期処理からの結果取得
        Console.WriteLine($"計算結果: {result}");
    }
}
結果を返さない非同期処理完了
計算結果: 42

CS1997 エラーの発生原因

return 文による不整合

CS1997 エラーは、非同期メソッド内で return文にオブジェクト式を指定している場合に発生します。

具体的には、戻り値が Task であるメソッドで、return文で値を返すときにこのエラーが発生します。

例えば、以下のコードでは、Task を返すメソッドで return await Task.Factory.StartNew(() => 1); と記述しているため、エラーとなります。

このような場合、非同期処理の結果として返却すべきは待機可能な操作そのものであり、直接値を返す形式にすべきではありません。

内部状態機械の動作との不一致

asyncメソッドはコンパイラによって内部状態機械に変換されます。

この状態機械は、戻り値に応じた型Task または Task<T>のラッパーとして動作するため、メソッド呼び出し時の整合性が求められます。

戻り値の型と実際の return文の内容が一致しない場合、コンパイラは状態機械生成の段階で不整合を検出し、CS1997 エラーを出力します。

すなわち、メソッド宣言の意図と実装の内容との間に齟齬があることを示しているのです。

コード例による検証

エラー発生コードのパターン

以下のサンプルコードは、Task を返す非同期メソッド内で値を返しているため、CS1997 エラーが発生するパターンです。

この例では、return await Task.Factory.StartNew(() => 1); によりエラーが引き起こされます。

using System;
using System.Threading.Tasks;
class Program
{
    // Task を返す非同期メソッドで値を返そうとしているためエラーが発生する例
    public static async Task ErrorExample()
    {
        // 以下の return 文はコンパイラエラー CS1997 を発生させます
        return await Task.Factory.StartNew(() => 1);
    }
    static async Task Main(string[] args)
    {
        try
        {
            await ErrorExample();
        }
        catch (Exception ex)
        {
            Console.WriteLine("例外発生: " + ex.Message);
        }
    }
}
// コンパイル時に以下のようなエラーが出力されます。
// エラー CS1997: 'ErrorExample': async メソッド内の return ステートメントで値を返すことはできません。

正しい記述方法との対照

async/await の利用方法の見直し

値を返さない非同期処理の場合は、return文を削除し、単に await を用いて処理の完了を待つ形に修正します。

以下のコードは、非同期処理中の値の返却を行わず、処理結果の表示のみを行う例です。

using System;
using System.Threading.Tasks;
class Program
{
    // 非同期処理内で await を利用し、return 文による値の返却を行わない実装例
    public static async Task CorrectAsyncExample()
    {
        await Task.Factory.StartNew(() =>
        {
            // 処理のシミュレーション
            Console.WriteLine("非同期処理中 (async/await 使用)");
        });
    }
    static async Task Main(string[] args)
    {
        await CorrectAsyncExample();
    }
}
非同期処理中 (async/await 使用)

非 async メソッドへの切り替え

非同期メソッドとして定義する必要がない場合は、async 修飾子および await キーワードを削除して、通常の Task を返すメソッドとして実装する方法もあります。

以下のコードは、Main関数で結果の待機方法として Wait() を利用している例です。

using System;
using System.Threading.Tasks;
class Program
{
    // async 修飾子を使わずに Task を返すメソッドの実装例
    public static Task CorrectNonAsyncExample()
    {
        return Task.Factory.StartNew(() =>
        {
            // 処理のシミュレーション
            Console.WriteLine("非同期処理中 (非 async 実装)");
        });
    }
    static void Main(string[] args)
    {
        // Task の完了を待機するために Wait() を使用
        CorrectNonAsyncExample().Wait();
    }
}
非同期処理中 (非 async 実装)

対処方法と修正時の留意点

修正手法の選択肢

CS1997 エラーが発生した場合の対処方法として、以下の選択肢があります。

  • 戻り値が不要な場合は、非同期メソッド内の return 文から値の返却部分を削除し、await だけを使用する方法
  • 非同期処理の結果として値を返す必要がある場合は、メソッドの戻り値の型を Task<T> に変更する方法
  • そもそも非同期処理として実装する必要がなければ、async 修飾子および await キーワードを取り除き、通常の Task を返却するメソッドとして実装する方法

これらの手法のうち、目的に応じた最適な方法を選ぶとともに、メソッドの意図と戻り値の整合性に十分注意する必要があります。

実装上の注意ポイント

非同期処理の実装に関しては、以下のポイントに注意してください。

  • メソッド宣言の戻り値型と return 文で返す値の型が一致していることを確認する
  • async メソッドは内部で状態機械に変換されるため、複雑な戻り値の操作は予期せぬ動作を引き起こさないよう、シンプルな実装を心がける
  • コードの可読性を保つため、非同期処理の設計意図に合わせた適切な戻り値の型Task または Task<T>を選択する
  • 非同期処理のテストを行う際は、Main 関数内での正しい待機方法(awaitWait() の使用)に注意する

以上の点を踏まえることで、CS1997 エラーを未然に防ぎ、安定した非同期処理の実装が可能となります。

まとめ

この記事では、C#の非同期処理における基本や、async 修飾子と await の動作、さらには TaskTask<T> の使い分けについて解説しています。

また、コンパイラエラー CS1997 の原因―非同期メソッド内での return文による不整合と内部状態機械との不一致―を具体的なコード例を通して説明し、その対処方法と実装上のポイントも紹介しています。

これにより、安全かつ効率的な非同期処理の実装方法が理解できます。

関連記事

Back to top button
目次へ