Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 

C# 早わかりリファレンス

― 6/6ページ: ラムダ式/非同期処理(async/await) ―

2024年4月25日

ラムダ式

ラムダ式の宣言

C#
using System;
using System.Linq;

class Program
{
  static void Main(string[] args)
  {
    Action a1 = () =>
    {
      Console.WriteLine("Action");
    };
    // Action
    a1();
    Action<int> a2 = i => Console.WriteLine(i);
    // 2
    a2(2);

    Func<int> f1 = () => 1;
    // 1
    f1();
    Func<string, string, int> f2 = (s1, s2) => int.Parse(s1 + s2);
    // 54
    f2.Invoke("5", "4");

    // LINQ
    // 9,1
    new[] { 1, 3, 5 }
    .Where(i => i <= 3)
    .Select(i => i * i)
    .OrderByDescending(i => i);

    // イベント
    Changed += (s, ea) => { Console.WriteLine("OnChanged"); };
  }
  
  static event EventHandler Changed;
}
リスト19-1 ラムダ式

 ラムダ式はデリゲート型などを簡潔に記述するための文法である。ラムダ式を暗黙的な型宣言varに代入することはできないが、指定された型に変換できるようにコンパイラーが判断する。基本的な記述方法は、(引数) => {式ブロック}であり、引数が1つの場合は()が省略でき、返り値の型は式ブロック内でreturnしているインスタンスの型で判断する。

 LINQのメソッド構文を記述するときやイベントの購読処理を記述するときによく利用される。

非同期処理(async/await)

C#
using System;
using System.Diagnostics;
using System.Threading.Tasks;

class Program
{
  static void Main(string[] args)
  {
    // Mainメソッドはasyncにできない
    // 非同期メソッドを同期的に待機する場合、GetAwaiter().GetResult() が使える
    ExecuteAsync().GetAwaiter().GetResult();
  }

  static async Task ExecuteAsync()
  {
    var sw = Stopwatch.StartNew();
    // 2秒待機
    await TestAsync();
    sw.Stop();
    // await: 2 [sec]
    Console.WriteLine($"await: {sw.Elapsed.Seconds} [sec]");
    sw.Restart();
    // この時点では待機しない
    var task = TestAsync();
    // 未await: 0 [sec]
    Console.WriteLine($"未await: {sw.Elapsed.Seconds} [sec]");
    await task;
    // await: 2 [sec]
    Console.WriteLine($"await: {sw.Elapsed.Seconds} [sec]");
    sw.Restart();
    // 一度完了したTaskをawaitすると、すぐに完了する
    await task;
    // 再await: 0 [sec]
    Console.WriteLine($"再await: {sw.Elapsed.Seconds} [sec]");

    var task2 = TestAsync();
    sw.Restart();

    sw.Restart();
    // async voidメソッドを呼んでも待機しない
    VoidAsync();
    // async void: 0 [sec]
    Console.WriteLine($"async void: {sw.Elapsed.Seconds} [sec]");

    sw.Restart();
    var r1 = await TestAsync2();
    // await Task<int>: Result=1 2 [sec]
    Console.WriteLine($"await Task<int>: Result={r1} {sw.Elapsed.Seconds} [sec]");

    var t2 = TestAsync2();
    sw.Restart();
    // Task<T> のResultプロパティで結果を取得できるが、プロパティアクセスで待機することになる
    var r2 = t2.Result;
    // Get Task<int>.Result: Result=1 2 [sec]
    Console.WriteLine($"Get Task<int>.Result: Result={r2} {sw.Elapsed.Seconds} [sec]");

    sw.Restart();
    // await同様、一度完了したTaskのResultプロパティは待機せずに取得できる
    var r3 = t2.Result;
    // Get Task<int>.Result again: Result=1 0 [sec]
    Console.WriteLine($"Get Task<int>.Result again: Result={r3} {sw.Elapsed.Seconds} [sec]");
  }

  static async Task TestAsync()
  {
    await Task.Delay(TimeSpan.FromSeconds(2));
  }

  static async void VoidAsync()
  {
    await Task.Delay(TimeSpan.FromSeconds(2));
  }

  static async Task<int> TestAsync2()
  {
    await Task.Delay(TimeSpan.FromSeconds(2));
    return 1;
  }
}
リスト20-1 非同期関数

 C#ではasync修飾子を持つ関数を非同期関数と呼び、非同期という用語はこのasyncを扱う説明に使っている。C#の非同期プログラミングはasyncのみならず、APIも利用できるが、この記事ではasyncawaitの使い方を中心とした説明にとどめる。

 非同期関数は返り値がTask型およびそのジェネリクス型のTask<TResult>(いずれもSystem.Threading.Tasks名前空間)、もしくはvoidである必要がある。TaskTask<TResult>型を返り値とする非同期関数は、await式により結果の取得を待機できる。サンプルコードでは非同期に2秒時間がかかる処理をしているが、awaitすることで呼び出し側が2秒待機していることが分かる。

 await Taskvoidに相当し返り値を持たず、await Task<TResult>TResult型に相当する返り値を持つ。非同期関数をawaitしない場合は、待機せず、その返り値であるタスクをawaitする、もしくはResultプロパティにアクセスしようとした時点で待機する。一度待機して完了したタスクは、再度結果を参照する時には待機しない(=再実行されない)。

 async voidな非同期関数は待機できない。主にイベントハンドラーの購読に登録する場合に利用することが多いが、例外がスローされた場合の処理が複雑になりがちであり、注意が必要である。

 以上でC#の主要な文法を一通り説明した。文法を羅列するだけでもかなりの項目数だったが、本稿では実利用者目線でできるだけコンパクトにまとまるように努力した。ぜひ、日々のコーディングの「あれ、どう書くんだっけ?」という場面で役立てていただけるとうれしい。

 [この記事の目次と索引(キーワード一覧)]はこちら

サイトからのお知らせ

Twitterでつぶやこう!