ラムダ式って苦手なんです。ぶっちゃけいらないですよね?

C# を始めたころ、そんな風に考えていましたが今では普通に関数書く方がダルいよね? と見事にオセロをひっくり返された私です。

ただ、お陰で多数の記述方法があって「書き方がいろいろあってわからないんだけど?」と困ってしまう要因にもなっていると思います。
そんな方でも、「わかった!」と言えるように順を追って解説していこうと思います。

unity を使っていますが、概念は C# です。

Button の OnClick イベントを登録する

ボタンを押したらコンソールログが出るようなやつ。初心者向け解説では結構出てきますよね。
実際に作ってみましょう。

UI/Button です

ButtonResponse.cs(Button に貼り付けます)

public class ButtonResponse : MonoBehaviour
{
    public void OnClickButton()
    {
        Debug.Log("Click Button");
    }
}
作業前
作業後
  • Button ResponseNone (Object) にドラッグ
  • No Function > ButtonResponse > OnClickButton を選択

以上で実行すると、ボタンを押す度にログが表示されます。

Console が表示されていない場合は、ウィンドウ左上の Window > General > Console を選択して表示

インスペクタを使わず、スクリプトで記述する

慣れてくると、インスペクタのイベント登録が「ちょっと怖いな…」と思ったりします。
試しに OnClickButton() ってメソッド名を OnClickButton2() にしてみましょう。

Missing ってなんやねん

名前変える度に、変化のあったボタンをインスペクタで再設定なんてやってられません。
これをなんとかしようとすると、スクリプトで OnClick イベントを記述する、という方法に行きつくと思います。

ButtonResponse.cs(先ほどのコードを上書き)

using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Button))]

public class ButtonResponse : MonoBehaviour
{
    void Awake()
    {
        var button = GetComponent<Button>();

        button.onClick.AddListener( OnClickButton2 );
    }

    public void OnClickButton2()
    {
        Debug.Log("Click Button");
    }
}

これで実行すると、インスペクタの設定がなんであろうと(たとえ Missing であっても)動作するようになります。

とはいえスクリプトで OnClick する場合、インスペクタの方は消しておきましょう。

button.onClick.AddListner() がイベントの正体です。これが OnClickButton2 を呼び出しているため、正しく動作します。

ここでラムダ式の登場

サンプル程度なら特に気にしませんでしたが、何百回、何千回と ButtonResponse.cs のように書いてるとだんだん面倒になってきます
もっと横着して書けないか。それを叶えてくれるのがラムダ式です。

式木がどうのとかは、知らなくて問題ありません。「楽に書くための省略記法」でいいと思います。

ラムダ記法を使って ButtonResponse.cs を書き換えるとこうなります。

using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Button))]

public class ButtonResponse : MonoBehaviour
{
    void Awake()
    {
        var button = GetComponent<Button>();

        button.onClick.AddListener( () => Debug.Log("Click Button") );
    }
}

OnClickButton2 がなくなりました。というより、AddListner の中に入りました。
この時、メソッドの書き方もなんか変わっています。

public void OnClickButton2()
{
    Debug.Log("Click Button");
}

↓

() => Debug.Log("Click Button")

なにやら => とか出てきて今まで覚えた事が無駄になりそうな不安を覚えますが、元を極限まで簡単に書いただけです。実際は、こんな感じで省略の段階があります。色のついた箇所に注目してください。

public void OnClickButton2()
{
Debug.Log("Click Button");
}



(1) => を書くのはラムダ式の決まり事
() =>
{
Debug.Log("Click Button");
}



(2) 最も簡略化した場合、(完全な式となるためか)最後のセミコロンもなくなる
() => Debug.Log("Click Button")

なお、(1) の書き方でも構いません。こちらの方が、メソッドっぽさは残っていますね。
命令を複数にする場合は、こちらの記法にした方が見やすいと思います。

button.onClick.AddListener(
    () =>
    {
        Debug.Log("Click");
        Debug.Log("Button");
    }
 );

引数ある場合は?

引数がある場合も紹介しておきます。今度は TMP_InputField でやってみます。

UI/InputField - Text Mesh Pro

InputResponse.cs: ラムダ使わない

using TMPro;
using UnityEngine;

public class InputResponse : MonoBehaviour
{
    void Awake()
    {
        var input = GetComponent<TMP_InputField>();

        input.onEndEdit.AddListener( OnEndEdit );
    }

    public void OnEndEdit(string result)
    {
        Debug.Log($"text: {result}");
    }
}

InputResponse.cs: ラムダ使う

using TMPro;
using UnityEngine;

public class InputResponse : MonoBehaviour
{
    void Awake()
    {
        var input = GetComponent<TMP_InputField>();

        input.onEndEdit.AddListener( result => Debug.Log($"text: {result}") );
    }
}

おいおい、() どこいったんだよ。と思う人はルールを見極める力が高いですね。
実際はこんな形で省略されました。
result のカッコを書かなくても、C# は正しく解釈してくれるわけです。

input.onEndEdit.AddListener( (result) => Debug.Log($"text: {result}") );
↓
input.onEndEdit.AddListener( result => Debug.Log($"text: {result}") );

List の検索とか使いやすい

ゲームを作りこめば必ず必要になるのがデータ管理ですが、サラッとラムダ式で書くことができます。例えばステージ情報のリストで、特定のステージタイプ・レベルだけ抽出したい、なんて時。

Row row = Rows.Find( row => row.StageType == type && row.Level == level );

動くサンプルコード

using System.Collections.Generic;
using UnityEngine;

public class SearchTest : MonoBehaviour
{
    public enum eStageType
    {
	    Room,
	    Forest,
	    City,
    };

    public class Row
    {
        public int        ID;
        public int        Group;
        public int        Level;
        public eStageType StageType;
    };

    List<Row> Rows;

    private void Start()
    {
        Rows = new List<Row>();
        Rows.Add(new Row() { Level = 0, StageType = eStageType.Room });
        Rows.Add(new Row() { Level = 1, StageType = eStageType.Room });
        Rows.Add(new Row() { Level = 0, StageType = eStageType.Forest });
        Rows.Add(new Row() { Level = 1, StageType = eStageType.Forest });
        Rows.Add(new Row() { Level = 0, StageType = eStageType.City });
        Rows.Add(new Row() { Level = 1, StageType = eStageType.City });

        // Room Level:0 を検索して返す
        var type  = eStageType.Room;
        var level = 0;
        Row row = Rows.Find( row => row.StageType == type && row.Level == level );
    }
}

var typevar level に注目してください。このように、メソッドであれば引数渡しが必要だったものも、不要になります。

ラムダの利点・欠点

思いつく利点・欠点を列挙しておきます。

記述が簡単になる

引数の型とか気にせず(var すらかかない)、少ないタイプでかけるのは魅力。
メソッドであれば引数渡しが必要だった部分の省略は、コードが込み入ってくるほど有難みがわかるでしょう。

イベントの内容と、コールする場所が近いので、プログラムを追いやすい

今回のソースコード程度だとたいして変わりませんが、500行を超えてくる初見コードだと、この差は大きいかもしれません。

普通の書き方とラムダの書き方が混在できるため、両方覚えていないといけない

どちらも正解ゆえ、解説サイトによって書き方が異なり、初心者にとっては混乱の元かも?

まとめると…

「デメリットは理解するまで」、ですね。覚えればメリットしかない!

今まで使っていなかった方も、これを機に使ってみてはいかがでしょうか?
みなさんのお役に立てる事を祈っています。

ここまで話聞いたけど、やっぱ利点わからんわーと思った場合、使わない選択肢ももちろんアリです。
でも、使えば使うほど戻れなくなること請け合い。
ちょっとずつ慣れていくといいと思います。

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA