[C#] awaitで待機した処理の戻り値を取得する方法を解説

C#でawaitを使用して非同期処理の戻り値を取得するには、Task<T>型の戻り値を持つメソッドをawaitします。

awaitは非同期メソッドの完了を待機し、その結果を取得します。

例えば、Task<int>を返すメソッドをawaitすると、int型の結果が得られます。

awaitは非同期処理が完了するまで現在のスレッドをブロックせず、処理が完了した時点で結果を返します。

この記事でわかること
  • C#における非同期メソッドの作成方法
  • awaitを使用した戻り値の取得方法
  • 複数の非同期処理を待機する方法
  • エラーハンドリングの実装方法
  • 非同期処理の応用例と最適化手法

目次から探す

awaitで戻り値を取得する方法

C#における非同期プログラミングでは、awaitキーワードを使用して非同期メソッドの処理が完了するのを待ち、その戻り値を取得することができます。

このセクションでは、Task<T>を使った非同期メソッドの作成方法や、戻り値の取得方法について詳しく解説します。

Task<T>を使った非同期メソッド

Task<T>は、非同期メソッドが戻り値を持つ場合に使用される型です。

Tは戻り値の型を示します。

以下は、Task<int>を返す非同期メソッドの例です。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task<int> CalculateAsync()
    {
        // 2秒待機する
        await Task.Delay(2000);
        return 42; // 計算結果を返す
    }
    static async Task Main(string[] args)
    {
        int result = await CalculateAsync(); // 戻り値を取得
        Console.WriteLine($"計算結果: {result}");
    }
}
計算結果: 42

この例では、CalculateAsyncメソッドが2秒待機した後に42を返します。

Mainメソッドでは、awaitを使ってCalculateAsyncの戻り値を取得しています。

awaitで戻り値を取得する基本的な例

awaitを使用することで、非同期メソッドの処理が完了するのを待ち、その戻り値を簡単に取得できます。

以下は、基本的な例です。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task<string> GetGreetingAsync()
    {
        await Task.Delay(1000); // 1秒待機
        return "こんにちは"; // 挨拶を返す
    }
    static async Task Main(string[] args)
    {
        string greeting = await GetGreetingAsync(); // 戻り値を取得
        Console.WriteLine(greeting);
    }
}
こんにちは

この例では、GetGreetingAsyncメソッドが1秒待機した後に「こんにちは」を返します。

Mainメソッドawaitを使ってその戻り値を取得し、コンソールに出力しています。

戻り値の型に応じた処理

非同期メソッドの戻り値の型に応じて、処理を変えることができます。

例えば、戻り値がintの場合とstringの場合で異なる処理を行うことができます。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task<int> GetNumberAsync()
    {
        await Task.Delay(500); // 0.5秒待機
        return 10; // 数値を返す
    }
    static async Task<string> GetTextAsync()
    {
        await Task.Delay(500); // 0.5秒待機
        return "テキスト"; // テキストを返す
    }
    static async Task Main(string[] args)
    {
        int number = await GetNumberAsync(); // int型の戻り値を取得
        string text = await GetTextAsync(); // string型の戻り値を取得
        Console.WriteLine($"数値: {number}, テキスト: {text}");
    }
}
数値: 10, テキスト: テキスト

この例では、GetNumberAsyncメソッドが整数を、GetTextAsyncメソッドが文字列を返します。

Mainメソッドでそれぞれの戻り値を取得し、コンソールに出力しています。

voidを返す非同期メソッドの扱い

非同期メソッドは、戻り値がない場合でもasyncキーワードを使って定義できます。

この場合、戻り値の型はTaskになります。

以下は、voidを返す非同期メソッドの例です。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task PrintMessageAsync()
    {
        await Task.Delay(1000); // 1秒待機
        Console.WriteLine("メッセージを表示しました。");
    }
    static async Task Main(string[] args)
    {
        await PrintMessageAsync(); // 非同期メソッドを呼び出す
    }
}
メッセージを表示しました。

この例では、PrintMessageAsyncメソッドが1秒待機した後にメッセージを表示します。

戻り値はないため、Task型で定義されています。

Mainメソッドawaitを使って呼び出しています。

非同期メソッドの作成方法

C#では、非同期メソッドを作成するためにasyncキーワードとTaskまたはTask<T>を使用します。

このセクションでは、非同期メソッドの作成方法や、戻り値の有無に応じたメソッドの定義について詳しく解説します。

asyncキーワードの使い方

asyncキーワードは、メソッドが非同期であることを示します。

このキーワードをメソッドの定義に追加することで、awaitを使用して非同期処理を待機することができます。

以下は、asyncキーワードの使い方の例です。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        await DisplayMessageAsync(); // 非同期メソッドを呼び出す
    }
    static async Task DisplayMessageAsync()
    {
        await Task.Delay(1000); // 1秒待機
        Console.WriteLine("非同期メッセージを表示しました。");
    }
}
非同期メッセージを表示しました。

この例では、DisplayMessageAsyncメソッドasyncキーワードを使って定義されており、awaitを使って1秒待機した後にメッセージを表示します。

TaskとTask<T>の使い分け

非同期メソッドの戻り値の型によって、TaskまたはTask<T>を使い分けます。

Taskは戻り値がない場合に使用し、Task<T>は戻り値がある場合に使用します。

スクロールできます
戻り値の型使用する型説明
戻り値なしTask処理が完了したことを示す
戻り値ありTask<T>処理の結果を返す

以下は、TaskTask<T>の使い分けの例です。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        await PrintMessageAsync(); // 戻り値なしの非同期メソッドを呼び出す
        int result = await CalculateAsync(); // 戻り値ありの非同期メソッドを呼び出す
        Console.WriteLine($"計算結果: {result}");
    }
    static async Task PrintMessageAsync()
    {
        await Task.Delay(500); // 0.5秒待機
        Console.WriteLine("メッセージを表示しました。");
    }
    static async Task<int> CalculateAsync()
    {
        await Task.Delay(500); // 0.5秒待機
        return 100; // 計算結果を返す
    }
}
メッセージを表示しました。
計算結果: 100

非同期メソッドの例

非同期メソッドは、asyncキーワードを使って定義し、awaitを使って非同期処理を待機します。

以下は、非同期メソッドの具体的な例です。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        string message = await GetMessageAsync(); // 戻り値を取得
        Console.WriteLine(message);
    }
    static async Task<string> GetMessageAsync()
    {
        await Task.Delay(2000); // 2秒待機
        return "非同期メッセージ"; // メッセージを返す
    }
}
非同期メッセージ

この例では、GetMessageAsyncメソッドが2秒待機した後に「非同期メッセージ」を返します。

Mainメソッドでその戻り値を取得し、コンソールに出力しています。

戻り値がない場合の非同期メソッド

戻り値がない非同期メソッドは、Task型で定義します。

以下は、戻り値がない非同期メソッドの例です。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        await PerformActionAsync(); // 戻り値なしの非同期メソッドを呼び出す
    }
    static async Task PerformActionAsync()
    {
        await Task.Delay(1500); // 1.5秒待機
        Console.WriteLine("アクションを実行しました。");
    }
}
アクションを実行しました。

この例では、PerformActionAsyncメソッドが1.5秒待機した後に「アクションを実行しました。」と表示します。

戻り値はないため、Task型で定義されています。

Mainメソッドawaitを使って呼び出しています。

エラーハンドリング

非同期プログラミングにおいても、エラーハンドリングは重要です。

C#では、try-catchブロックを使用して例外を捕捉し、適切に処理することができます。

このセクションでは、非同期メソッドにおけるエラーハンドリングの方法について詳しく解説します。

try-catchを使った例外処理

try-catchブロックを使用することで、非同期メソッド内で発生した例外を捕捉し、適切に処理することができます。

以下は、try-catchを使った例外処理の例です。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        try
        {
            await CauseExceptionAsync(); // 例外を引き起こす非同期メソッドを呼び出す
        }
        catch (Exception ex)
        {
            Console.WriteLine($"例外が発生しました: {ex.Message}");
        }
    }
    static async Task CauseExceptionAsync()
    {
        await Task.Delay(1000); // 1秒待機
        throw new InvalidOperationException("無効な操作が行われました。"); // 例外をスロー
    }
}
例外が発生しました: 無効な操作が行われました。

この例では、CauseExceptionAsyncメソッドが例外をスローし、Mainメソッド内のtry-catchブロックでその例外を捕捉しています。

非同期メソッド内での例外の扱い

非同期メソッド内で発生した例外は、awaitを使用して呼び出したメソッドの呼び出し元に伝播します。

これにより、呼び出し元で例外を捕捉することができます。

以下は、その例です。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        try
        {
            await ThrowExceptionAsync(); // 例外を引き起こす非同期メソッドを呼び出す
        }
        catch (Exception ex)
        {
            Console.WriteLine($"例外が発生しました: {ex.Message}");
        }
    }
    static async Task ThrowExceptionAsync()
    {
        await Task.Delay(500); // 0.5秒待機
        throw new Exception("エラーが発生しました。"); // 例外をスロー
    }
}
例外が発生しました: エラーが発生しました。

この例では、ThrowExceptionAsyncメソッドが例外をスローし、Mainメソッドでその例外を捕捉しています。

awaitでの例外処理の注意点

awaitを使用する際には、例外が発生する可能性があることを考慮する必要があります。

awaitを使ったメソッド呼び出しは、例外が発生した場合にその例外を呼び出し元に伝播します。

したがって、awaitを使用する際は、必ずtry-catchブロックで囲むことが推奨されます。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        try
        {
            await PerformRiskyOperationAsync(); // リスキーな操作を実行
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラーが発生しました: {ex.Message}");
        }
    }
    static async Task PerformRiskyOperationAsync()
    {
        await Task.Delay(1000); // 1秒待機
        throw new Exception("リスキーな操作中にエラーが発生しました。"); // 例外をスロー
    }
}
エラーが発生しました: リスキーな操作中にエラーが発生しました。

Taskの例外とawaitの関係

Taskオブジェクトは、非同期メソッドの実行結果を表します。

非同期メソッドが例外をスローした場合、その例外はTaskオブジェクトに格納されます。

awaitを使用すると、Taskの例外が呼び出し元に伝播し、try-catchブロックで捕捉することができます。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        Task task = ThrowExceptionAsync(); // 例外を引き起こす非同期メソッドを呼び出す
        try
        {
            await task; // awaitで例外を捕捉
        }
        catch (Exception ex)
        {
            Console.WriteLine($"タスクで例外が発生しました: {ex.Message}");
        }
    }
    static async Task ThrowExceptionAsync()
    {
        await Task.Delay(500); // 0.5秒待機
        throw new Exception("タスク中にエラーが発生しました。"); // 例外をスロー
    }
}
タスクで例外が発生しました: タスク中にエラーが発生しました。

この例では、ThrowExceptionAsyncメソッドが例外をスローし、その例外がTaskオブジェクトに格納されます。

awaitを使用してその例外を捕捉し、適切に処理しています。

複数の非同期処理を待機する方法

C#では、複数の非同期処理を同時に実行し、その結果を待機することができます。

これにより、プログラムの効率を向上させることができます。

このセクションでは、Task.WhenAllTask.WhenAnyを使った複数の非同期処理の待機方法について解説します。

Task.WhenAllの使い方

Task.WhenAllは、複数の非同期タスクを同時に実行し、すべてのタスクが完了するのを待つためのメソッドです。

すべてのタスクが成功した場合、結果を取得することもできます。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        Task task1 = Task1Async(); // 非同期タスク1
        Task task2 = Task2Async(); // 非同期タスク2
        await Task.WhenAll(task1, task2); // すべてのタスクが完了するのを待つ
        Console.WriteLine("すべてのタスクが完了しました。");
    }
    static async Task Task1Async()
    {
        await Task.Delay(2000); // 2秒待機
        Console.WriteLine("タスク1が完了しました。");
    }
    static async Task Task2Async()
    {
        await Task.Delay(1000); // 1秒待機
        Console.WriteLine("タスク2が完了しました。");
    }
}
タスク2が完了しました。
タスク1が完了しました。
すべてのタスクが完了しました。

この例では、Task1AsyncTask2Asyncの2つの非同期タスクを同時に実行し、両方のタスクが完了するのを待っています。

Task.WhenAnyの使い方

Task.WhenAnyは、複数の非同期タスクの中で最初に完了したタスクを待機するためのメソッドです。

最初に完了したタスクの結果を取得することができます。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        Task task1 = Task1Async(); // 非同期タスク1
        Task task2 = Task2Async(); // 非同期タスク2
        Task completedTask = await Task.WhenAny(task1, task2); // 最初に完了したタスクを待つ
        Console.WriteLine($"完了したタスク: {completedTask.Id}");
    }
    static async Task Task1Async()
    {
        await Task.Delay(2000); // 2秒待機
        Console.WriteLine("タスク1が完了しました。");
    }
    static async Task Task2Async()
    {
        await Task.Delay(1000); // 1秒待機
        Console.WriteLine("タスク2が完了しました。");
    }
}
タスク2が完了しました。
完了したタスク: 2
タスク1が完了しました。

この例では、Task2Asyncが最初に完了し、そのタスクのIDを表示しています。

複数のawaitを使った処理の例

複数のawaitを使って、非同期処理を順番に実行することもできます。

以下は、その例です。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        await Task1Async(); // 最初の非同期タスクを待つ
        await Task2Async(); // 次の非同期タスクを待つ
        Console.WriteLine("すべてのタスクが完了しました。");
    }
    static async Task Task1Async()
    {
        await Task.Delay(2000); // 2秒待機
        Console.WriteLine("タスク1が完了しました。");
    }
    static async Task Task2Async()
    {
        await Task.Delay(1000); // 1秒待機
        Console.WriteLine("タスク2が完了しました。");
    }
}
タスク1が完了しました。
タスク2が完了しました。
すべてのタスクが完了しました。

この例では、Task1Asyncが完了した後にTask2Asyncが実行され、すべてのタスクが完了したことを表示します。

戻り値を持つ複数の非同期処理の結果を取得する方法

戻り値を持つ複数の非同期処理の結果を取得するには、Task.WhenAllを使用して、すべてのタスクの結果を配列として取得します。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        Task<int> task1 = Task1Async(); // 戻り値を持つ非同期タスク1
        Task<int> task2 = Task2Async(); // 戻り値を持つ非同期タスク2
        int[] results = await Task.WhenAll(task1, task2); // すべてのタスクの結果を取得
        Console.WriteLine($"タスク1の結果: {results[0]}, タスク2の結果: {results[1]}");
    }
    static async Task<int> Task1Async()
    {
        await Task.Delay(2000); // 2秒待機
        return 10; // 結果を返す
    }
    static async Task<int> Task2Async()
    {
        await Task.Delay(1000); // 1秒待機
        return 20; // 結果を返す
    }
}
タスク2の結果: 20, タスク1の結果: 10

この例では、Task1AsyncTask2Asyncがそれぞれ整数を返し、Task.WhenAllを使用して両方の結果を取得しています。

結果は配列として格納され、コンソールに出力されます。

応用例

非同期プログラミングは、さまざまなシナリオで活用できます。

このセクションでは、非同期処理のキャンセル、タイムアウト設定、UIスレッドとの関係、パフォーマンス最適化について解説します。

非同期処理のキャンセル

非同期処理をキャンセルするためには、CancellationTokenを使用します。

これにより、実行中のタスクを安全に中断することができます。

using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        Task task = LongRunningOperationAsync(cts.Token);
        // 1秒後にキャンセルを要求
        await Task.Delay(1000);
        cts.Cancel();
        try
        {
            await task; // タスクの完了を待つ
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("操作がキャンセルされました。");
        }
    }
    static async Task LongRunningOperationAsync(CancellationToken token)
    {
        for (int i = 0; i < 5; i++)
        {
            token.ThrowIfCancellationRequested(); // キャンセル要求をチェック
            await Task.Delay(1000); // 1秒待機
            Console.WriteLine($"処理中... {i + 1}");
        }
    }
}

出力結果(キャンセルされた場合):

処理中... 1
操作がキャンセルされました。

この例では、LongRunningOperationAsyncメソッドが1秒ごとに処理を行い、1秒後にキャンセルが要求されます。

キャンセルが要求されると、OperationCanceledExceptionがスローされ、適切に処理されます。

非同期処理のタイムアウト設定

非同期処理にタイムアウトを設定することで、指定した時間内に処理が完了しない場合に自動的にキャンセルすることができます。

using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        using (CancellationTokenSource cts = new CancellationTokenSource())
        {
            cts.CancelAfter(2000); // 2秒後にキャンセル
            try
            {
                await LongRunningOperationAsync(cts.Token);
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("タイムアウトにより操作がキャンセルされました。");
            }
        }
    }
    static async Task LongRunningOperationAsync(CancellationToken token)
    {
        await Task.Delay(5000, token); // 5秒待機(キャンセル可能)
        Console.WriteLine("処理が完了しました。");
    }
}
タイムアウトにより操作がキャンセルされました。

この例では、LongRunningOperationAsyncメソッドが5秒待機しますが、2秒後にキャンセルが要求されるため、タイムアウトが発生します。

UIスレッドと非同期処理の関係

UIアプリケーションでは、UIスレッドがブロックされないように非同期処理を使用することが重要です。

非同期メソッドを使用することで、UIが応答し続けることができます。

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
public class MyForm : Form
{
    private Button myButton;
    public MyForm()
    {
        myButton = new Button { Text = "処理開始", Dock = DockStyle.Fill };
        myButton.Click += async (sender, e) => await StartLongRunningTaskAsync();
        Controls.Add(myButton);
    }
    private async Task StartLongRunningTaskAsync()
    {
        myButton.Enabled = false; // ボタンを無効化
        await Task.Delay(5000); // 5秒待機(非同期処理)
        MessageBox.Show("処理が完了しました。");
        myButton.Enabled = true; // ボタンを再度有効化
    }
    [STAThread]
    public static void Main()
    {
        Application.EnableVisualStyles();
        Application.Run(new MyForm());
    }
}

この例では、ボタンをクリックすると5秒間の非同期処理が開始され、UIがブロックされることなく、処理が完了した後にメッセージボックスが表示されます。

非同期処理のパフォーマンス最適化

非同期処理を最適化するためには、タスクの数や待機時間を適切に管理することが重要です。

以下は、非同期処理のパフォーマンスを向上させるためのポイントです。

  • タスクの並列実行: 複数の非同期タスクを同時に実行することで、全体の処理時間を短縮できます。
  • 適切な待機時間: 不要な待機時間を減らすことで、処理の効率を向上させます。
  • リソースの管理: 非同期処理で使用するリソース(スレッド、メモリなど)を適切に管理し、過剰なリソース消費を避けます。

以下は、複数の非同期タスクを並列に実行する例です。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main(string[] args)
    {
        Task<int> task1 = PerformCalculationAsync(1); // 非同期計算タスク1
        Task<int> task2 = PerformCalculationAsync(2); // 非同期計算タスク2
        int[] results = await Task.WhenAll(task1, task2); // 並列に実行し、結果を取得
        Console.WriteLine($"結果: {results[0]}, {results[1]}");
    }
    static async Task<int> PerformCalculationAsync(int id)
    {
        await Task.Delay(3000); // 3秒待機
        return id * 10; // 計算結果を返す
    }
}
結果: 10, 20

この例では、2つの非同期計算タスクを並列に実行し、全体の処理時間を短縮しています。

非同期処理を適切に活用することで、アプリケーションのパフォーマンスを向上させることができます。

よくある質問

awaitを使わないとどうなる?

awaitを使わない場合、非同期メソッドは即座に制御を呼び出し元に返します。

これにより、非同期処理が完了する前に次の処理が実行されるため、結果を正しく取得できないことがあります。

また、非同期メソッド内で例外が発生した場合、その例外は呼び出し元に伝播しないため、エラーハンドリングが難しくなります。

static async Task<int> CalculateAsync()
{
    await Task.Delay(1000);
    return 42;
}
static void Main(string[] args)
{
    var result = CalculateAsync(); // awaitを使わない
    Console.WriteLine(result.Result); // 結果を取得するためにResultプロパティを使用
}

この場合、result.Resultを使用すると、非同期処理が完了するまでブロックされるため、非推奨です。

非同期処理の戻り値がnullになるのはなぜ?

非同期処理の戻り値がnullになる場合、いくつかの理由が考えられます。

  1. 戻り値が明示的にnullに設定されている: 非同期メソッド内で戻り値をnullに設定している場合。
  2. 例外が発生した: 非同期メソッド内で例外が発生し、例外処理が行われなかった場合、戻り値がnullになることがあります。
  3. 非同期メソッドがvoidを返す: 戻り値がない非同期メソッドを呼び出した場合、戻り値は存在しません。
static async Task<string> GetMessageAsync()
{
    await Task.Delay(1000);
    return null; // 明示的にnullを返す
}
static async Task Main(string[] args)
{
    string message = await GetMessageAsync();
    Console.WriteLine(message ?? "メッセージはnullです。"); // nullチェック
}

この場合、GetMessageAsyncメソッドnullを返し、コンソールに「メッセージはnullです。」と表示されます。

非同期処理のデバッグ方法は?

非同期処理のデバッグは、通常の同期処理と少し異なります。

以下の方法で非同期処理をデバッグできます。

  1. ブレークポイントの設定: 非同期メソッド内にブレークポイントを設定し、awaitの前後で処理の流れを確認します。
  2. タスクの状態を確認: Taskオブジェクトの状態を確認することで、タスクが完了しているか、キャンセルされたか、エラーが発生したかを判断できます。
  3. 例外の捕捉: try-catchブロックを使用して、非同期メソッド内で発生した例外を捕捉し、エラーメッセージを確認します。
  4. デバッグ出力: Console.WriteLineDebug.WriteLineを使用して、処理の進行状況や変数の値を出力し、流れを追跡します。
static async Task<int> CalculateAsync()
{
    await Task.Delay(1000);
    throw new Exception("計算中にエラーが発生しました。"); // 例外をスロー
}
static async Task Main(string[] args)
{
    try
    {
        int result = await CalculateAsync();
        Console.WriteLine(result);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"エラー: {ex.Message}"); // 例外を捕捉
    }
}

この例では、CalculateAsyncメソッド内で例外が発生し、Mainメソッドでその例外を捕捉してエラーメッセージを表示します。

デバッグ時には、ブレークポイントを設定して処理の流れを確認することが重要です。

まとめ

この記事では、C#における非同期プログラミングの基本から応用まで、さまざまなトピックを取り上げてきました。

非同期メソッドの作成方法やエラーハンドリング、複数の非同期処理を待機する方法、さらには非同期処理のキャンセルやタイムアウト設定についても詳しく解説しました。

これらの知識を活用することで、より効率的で応答性の高いアプリケーションを開発することが可能になります。

ぜひ、実際のプロジェクトに非同期プログラミングを取り入れ、パフォーマンスの向上を体験してみてください。

  • URLをコピーしました!
目次から探す