CS801~2000

C# コンパイラ エラー CS1983 の原因と対処法を解説

CS1983エラーは、async修飾子を付けたメソッドの戻り値が不適切な型の場合に発生します。

非同期メソッドではvoidTaskTask<T>IAsyncEnumerable<T>IAsyncEnumerator<T>など、awaitを正しく扱える型を使用する必要があります。

例えば、同期的なIEnumerable<T>を使用するとエラーとなります。

適切な型に変更することで解決できます。

非同期メソッドの基礎

async修飾子の目的と動作

async 修飾子は、メソッド内で非同期処理を扱うために用いられます。

これにより、await キーワードが使えるようになり、時間のかかる処理をブロッキングせずに実行できるため、アプリケーションのレスポンスが向上します。

asyncメソッドはコンパイル時に特殊な状態機械へ変換され、非同期処理の完了後に結果を返すように設計されています。

戻り値に期待される型

asyncメソッドで使用できる戻り値の型は、voidTaskTask<T>、および task-like型、さらに IAsyncEnumerable<T>IAsyncEnumerator<T> などが挙げられます。

これらの型は、非同期処理の性質に合わせた結果を返すために必要な型として用意されています。

Task と Task<T> の特徴

Task は非同期処理の完了を表現するために使用される型で、戻り値が不要な場合に利用されます。

一方で、Task<T> は非同期処理の完了後に値を返す場合に用いられ、ジェネリクスの型引数 T を通して結果の型を指定します。

以下は、Task<int> を返す非同期メソッドのサンプルコードです。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        int result = await ComputeAsync();
        Console.WriteLine($"結果: {result}");
    }
    // 非同期計算を行うメソッド
    static async Task<int> ComputeAsync()
    {
        await Task.Delay(100); // 100ミリ秒待機
        return 42; // 計算結果を返す
    }
}
結果: 42

IAsyncEnumerable<T> と IAsyncEnumerator<T> の概要

IAsyncEnumerable<T> および IAsyncEnumerator<T> は、非同期にデータの列挙を可能にするためのインターフェイスです。

これらを利用することで、データの生成や取得を待ち時間を含む非同期処理として実行しながら、列挙処理を行うことができます。

特に、大量のデータや外部リソースとの連携時に役立ちます。

以下は、非同期に数値を生成するサンプルコードです。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        await foreach (var number in GenerateNumbersAsync())
        {
            Console.WriteLine($"数値: {number}");
        }
    }
    // 非同期に数列を生成するメソッド
    static async IAsyncEnumerable<int> GenerateNumbersAsync()
    {
        for (int i = 0; i < 3; i++)
        {
            await Task.Delay(100); // 各ループで100ミリ秒待機
            yield return i;
        }
    }
}
数値: 0
数値: 1
数値: 2

エラーCS1983発生の原因

戻り値型不整合によるエラー条件

エラー CS1983 は、asyncメソッドの戻り値型が、コンパイラで要求される型と不整合な場合に発生します。

たとえば、非同期処理内で await を使用しているにも関わらず、戻り値の型が IEnumerable<T> のような非同期処理に適していない型で定義されると、このエラーが発生します。

IEnumerable<T> 使用時の問題点

非同期メソッドで IEnumerable<T> を使用すると、yield returnawait を組み合わせた場合に互換性の問題が起こります。

これは、IEnumerable<T> が返す IEnumerator<T>MoveNextメソッドが、非同期の完了を表す Task<T> の戻り値を扱えないためです。

IEnumerator<T>との非互換性

  • IEnumerator<T>MoveNext メソッドは同期的にブール値 (bool) を返す設計となっているため、内部処理が Task<bool> のような非同期タスクとして扱われる場合に互換性がありません。
  • 非同期処理を行う際、列挙処理全体を非同期で制御することができず、エラーの原因となります。

コード例によるエラー事例

以下のサンプルコードは、asyncメソッドが IEnumerable<int> を返すことによりエラー CS1983 が発生する例です。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
    // このメソッドはエラー CS1983 を発生させます
    static async IEnumerable<int> GenerateNumbersError()
    {
        yield return await Task.FromResult(1);
    }
    static async Task Main(string[] args)
    {
        foreach (var number in GenerateNumbersError())
        {
            Console.WriteLine($"数値: {number}");
        }
    }
}
error CS1983: 非Taskを返すasyncメソッド 'GenerateNumbersError' は 'await' を使用できません。

対処法の具体例

正しい戻り値型への変更方法

エラーを回避するためには、asyncメソッドの戻り値型を非同期処理に適した型に変更する必要があります。

非同期に列挙処理を行う場合は、IAsyncEnumerable<T> を採用することで、awaityield return を正しく組み合わせることが可能になります。

IAsyncEnumerable<T> 採用手順

  1. 戻り値の型を IAsyncEnumerable<T> に変更する
  2. 呼び出し元では、await foreach を使用して非同期に列挙する
  3. 必要な名前空間 System.Collections.Generic をインポートする

以下は、修正後のサンプルコードです。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
    // エラー修正済み: 非同期に数を生成するメソッド
    static async IAsyncEnumerable<int> GenerateNumbersAsync()
    {
        yield return await Task.FromResult(1); // 非同期に結果を返す
    }
    static async Task Main(string[] args)
    {
        await foreach (var number in GenerateNumbersAsync())
        {
            Console.WriteLine($"数値: {number}");
        }
    }
}
数値: 1

修正前後のコード比較

修正前修正後
csharp
static async IEnumerable<int> GenerateNumbersError()
{
yield return await Task.FromResult(1);
}
csharp
static async IAsyncEnumerable<int> GenerateNumbersAsync()
{
yield return await Task.FromResult(1);
}

実装時の注意点とポイント

  • async メソッドにおける戻り値型は、非同期処理の完了や列挙処理を正しく制御できる型である必要があります。
  • yield return の前に await を使用する場合、戻り値の型が Task 対応になっていることを確認してください。
  • 呼び出し元での列挙処理は await foreach を使用するなど、非同期の特性に合った記述に変更するよう留意してください。
  • メソッドシグネチャの変更が関連部分に与える影響を考慮し、全体の整合性を保つことが重要です。

まとめ

この記事では、async修飾子の役割や非同期メソッドで使用可能な戻り値型(Task、Task<T>、IAsyncEnumerable<T>など)の違いと特徴を解説しています。

特に、非同期処理でIEnumerable<T>を使うと発生するCS1983エラーの原因を、IEnumerator<T>との非互換性という視点から説明。

また、正しい戻り値型への変更手順や修正例を交え、非同期メソッドの実装上の注意点も紹介しています。

関連記事

Back to top button
目次へ