C#の書き方を学習する:メソッドチェーン・コードブロック・ラムダ式・オブジェクト初期化子

コンピュータ

メソッドチェーン

代表例はLINQが挙げられます。

var result =
    items
        .Where(x => x.IsActive)
        .Select(x => x.Value)
        .OrderBy(x => x)
        .Take(10)
        .ToList();

この LINQ のコードは、

コンテナ系の items を対象に

  1. Where で条件に合う要素だけを絞り込み
  2. Select で必要な項目を取り出し
  3. OrderBy で並べ替え
  4. Take で先頭から n 件を抽出し
  5. ToList でコレクション化

その結果を result に代入しています。

メソッドチェーンを自前で書くとこんな感じになります。

public class Builder
{
    public Builder StepA()
    {
        // 何か処理をしたつもり
        return this; // ★ 自分自身を返す
    }

    public Builder StepB()
    {
        // 何か別の処理をしたつもり
        return this; // ★ 自分自身を返す
    }
}

StepAもStepBも戻り値でthisを返しています。
インタンスが返ることで「.」でつなげることが出来る仕掛けです。

var builder = new Builder();

builder
    .StepA()
    .StepB();

具体的な処理の実装は、
引数にデリゲートを取り、
そのデリゲートをラムダ式で渡すことで、
メソッドチェーンとして書けるようになります。

public class Builder
{
    public Builder Do(Action action)
    {
        action?.Invoke();
        return this; // 自分自身を返す
    }
}

使う側のコード

var builder = new Builder();

builder
    .Do(() => { /* 何か処理 */ })
    .Do(() => { /* 何か別の処理 */ });

コードブロック

素のコードブロックはスコープを作るための構文と考えられます。

{
    var temp = Compute();
    Use(temp);
}
// temp はここでは見えない

一般的にはifやusing、try-catch構文などに使う物であり、

素の状態では使うことは少ないですが、あえて意味を考えると

  1. 変数の寿命を限定できる
  2. 名前の衝突を防げる
  3. 「ここだけの一時処理」を明示できる

などがあげられます。

ラムダ式

ラムダ式は、デリゲート(またはデリゲート互換の型)を生成するための
無名関数を記述する構文です。

式タイプ

x => x + 1

引数はx、戻り値はx + 1
1つのメソッドを呼び出すだけのコードなどでよく使います。

コードブロックタイプ

x =>
{
    var y = x + 1;
    return y * 2;
}

{}の中は普通のコードブロックで、変数を持てます。
また、returnで戻り値を返すことも出来ます。

Actionタイプのデリゲート

Action a = () => Console.WriteLine("Hello");

// 実行
a();

Actionは戻り値なし

Funcタイプのデリゲート

Func<string, int> parse = s => int.Parse(s);

// 実行
int value = parse("123"); // 123

Funcは戻り値あり

具体例

処理時間を計測するコードは、一般的に次のような形になります。

using System.Diagnostics;
~ 省略 ~
var sw = Stopwatch.StartNew();

// 計測したい処理をここに書く 

sw.Stop();
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");

この処理をデリゲートとして切り出すと、以下のようになります。

処理時間を計測するデリゲート(使われる側)

using System.Diagnostics;

static void Measure(Action action)
{
    var sw = Stopwatch.StartNew();

    action();   // 渡された処理を実行

    sw.Stop();
    Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");
}

処理時間を計測したい処理側のラムダ式(使う側)

Measure(() =>
{
    // 計測したい処理を書く場所
});

このように、Stopwatch を使った計測処理を
デリゲートとして切り出すことで、

  • 毎回同じ計測コードを書く必要がなくなり
  • 計測したい処理だけをラムダ式として渡せるようになります

結果として、処理時間の計測を
デリゲート + ラムダ式 という構文に
置き換えることができます。

オブジェクト初期化子

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

var p = new Person
{
    Name = "Alice",
    Age = 20
};

オブジェクト初期化子は、

new した後にプロパティへ代入するコードの構文糖衣みたいな物。

オブジェクト名(p)が省略出来て便利。

var p = new Person();
p.Name = "Alice";
p.Age = 20;

さいごに

何となく使っていた言語機能を、あらためて整理してみました。

機能によっては、サンプルコードとして
スポット的に切り出してしまうと、
本来の使いどころが見えにくくなることもあります。

理解が深まったら再度整理し直したいと思います。

コメント