i-school - デリゲートとラムダ式の活用事例

活用事例


 様々な活用方法がありますので、実装例を提示します。

 デリゲートとラムダ式の理解が難しいポイントはいくつかありますが、2点抜粋します。

 ・「ラムダ式は、何を省略して書かれているのかが分かりづらい」 → メソッドを省略して書いている感覚が生まれない
 ・「実践するときに他の要素の理解を求められる」 → 理解しながら実践することがむずかしい

 たとえばイベントハンドラへの理解が無いままラムダ式を入れても、ラムダ式でメソッドを渡していることが分かりづらいですし、
両方の機能を利用している LINQ にしても、内部処理が分かりづらいです。

 そのため、ここでは具体的な活用事例を提示した上で、少しでも理解のお役に立てるように記事を作成します。


1.LINQとラムダ式


 C#では、LINQ (Language Integrated Query)という機能を使用して、データベース、リストや配列などのデータ構造に対してクエリ操作を行うことができます。
クエリとは、探したいデータの検索や更新、削除、抽出などの要求する命令のことを言います。

 LINQは、デリゲートとラムダ式と連携して、データの操作を簡潔で読みやすい形式で記述することができます。

 例えば、次のようなリストがあるとします。


List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

 このリストから、偶数だけを抽出する場合、LINQ の Where メソッドとラムダ式を使用して次のように記述できます。


var evenNumbers = numbers.Where(n => n % 2 == 0);



 また、リスト内の数値を2倍した新しいリストを作成する場合は、L
INQ の Select メソッドを活用することで実現できます。


var doubledNumbers = numbers.Select(n => n * 2);

 ラムダ式を使用して、操作の条件や変換処理を簡潔に記述する方法を提示しました。
これにより、データの操作がわかりやすくなります。



 以下はサンプルコードです。


using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class LambdaExample : MonoBehaviour
{
    [Serializable]
    public class Item
    {
        public string Name;
        public int Value;
    }

    [SerializeField]
    private List<Item> items;

    private void Start()
    {
        // ラムダ式を使って、Value が10以上のアイテムを検索するLINQクエリを定義します
        var expensiveItems = items.Where(item => item.Value >= 10);

        // 結果を表示します
        Debug.Log("値が10以上のアイテム:");
        foreach (var expensiveItem in expensiveItems)
        {
            Debug.Log($"アイテム名: {expensiveItem.Name}, 値: {expensiveItem.Value}");
        }
    }
}

 UnityのMonoBehaviourを継承したLambdaExampleクラスを定義しています。
Itemクラスは、アイテムの名前と値を持つシンプルなデータ構造です。

 Startメソッド内で、ラムダ式item => item.Value >= 10を使って、値が10以上のアイテムを検索するLINQクエリを定義しています。
そして、結果をUnityのデバッグログに表示しています。

 Itemオブジェクトのリストitemsは、Unityエディタ上で編集して値を設定できます。
このコードを実行すると、値が10以上のアイテムがログに表示されます。


2.DOTween の OnComplete での実装例


 以下は、DOTweenとラムダ式を活用して、ゲームオブジェクトを移動させた後にスケールを変更する例です。


using DG.Tweening;

void MoveAndScaleObject()
{
    transform.DOMove(new Vector3(5, 0, 0), 1f)
    .OnComplete(() => 
    {
          Debug.Log("Move Complete!");

          transform.DOScale(new Vector3(2, 2, 2), 1f)
        .OnComplete(() => Debug.Log("Scale Complete!"));
      });
}

 上記のコードでは、ゲームオブジェクトを移動させた後、OnCompleteメソッドを使ってスケールを変更する処理が実行されます。

 このように、ラムダ式を使ってデリゲートを簡潔に表現することで、複雑なアニメーション処理もわかりやすく記述できます。


3.ラムダ式を使用したイベントハンドラの定義


 デリゲートとラムダ式は、イベントハンドラの定義にも活用できます。

 イベントハンドラは、特定のイベントが発生したときに実行されるメソッドです。
デリゲートとラムダ式を使って、イベントハンドラを簡潔に定義することができます。

 例えば、ボタンのクリックイベントに対して、イベントハンドラを定義する場合、以下のようにラムダ式を使用できます。


using UnityEngine;
using UnityEngine.UI;

public class ButtonClickHandler : MonoBehaviour
{
    [SerializeField]
    private Button myButton;

    private void Start()
    {
        myButton.onClick.AddListener(() =>
        {
            // ボタンがクリックされたときの処理
            Debug.Log("ボタンがクリックされました");
        });
    }
}

 この例では、ラムダ式の左側の()は、イベントハンドラの引数を表します。
右側の{}は、イベントハンドラの本体で、実際に実行される処理を記述します。


4.高階関数とラムダ式


 高階関数は、関数を引数として受け取り、または関数を戻り値として返す関数です。
デリゲートとラムダ式を使って、高階関数を簡単に実装できます。

 例えば、リストに対して、特定の条件に一致する要素をフィルタリングする関数を作成する場合、以下のようにラムダ式を使用できます。


List<int> Filter(List<int> list, Predicate<int> predicate)
{
    List<int> result = new List<int>();
    foreach (var item in list)
    {
        if (predicate(item))
        {
            result.Add(item);
        }
    }
    return result;
}

 この関数は、listというリストと、predicateという条件を表す(述語)デリゲートを引数に取ります。
ラムダ式を使って、この関数を呼び出すことができます。


List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
List<int> evenNumbers = Filter(numbers, n => n % 2 == 0);

 この例では、n => n % 2 == 0というラムダ式を使って、偶数を判定する条件を表現しています。

 他にも色々な場所でデリゲートとラムダ式による記述は活用されています。


ラムダ式の利点と注意点


 ラムダ式は、コードを簡潔にし、可読性を向上させるために役立ちます。
ただし、適切に使用しないと、逆にコードの可読性が低下することがあります。

 以下は、ラムダ式の利点と注意点です。


1.利点


 ・コードが簡潔になり、可読性が向上する。
 ・名前のない匿名関数を作成できるため、一度しか使用しない処理を記述する際に便利。
 ・高階関数やLINQと連携して、データの操作を効率的に記述できる。


2.注意点


 ・ラムダ式が複雑になりすぎると、コードの可読性が低下する可能性がある。
 ・ラムダ式内で外部変数をキャプチャする場合、副作用やリソースリークに注意する必要がある。


まとめ


 デリゲートとラムダ式は、C#プログラムでメソッドを扱う際の強力なツールです。
これらを使ってコードをシンプルにし、読みやすくすることができます。

 デリゲートとラムダ式の関係を理解し、基本的な処理の読み解き方を理解しておくことで、
より効率的で柔軟なプログラミングが可能になります。



 最後にLINQと高階関数を利用したフィルタリング処理の実装例を提示します。


using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class FilterExample : MonoBehaviour
{
    [Serializable]
    public class Item
    {
        public string Name;
        public int Value;
    }

    public delegate bool ItemCondition(Item item);

    [SerializeField]
    private List<Item> items;

    private List<Item> FilterItems(ItemCondition condition)
    {
        return items.Where(condition).ToList();
    }

    private void ShowItems(string message, List<Item> filteredItems)
    {
        Debug.Log(message);
        foreach (var item in filteredItems)
        {
            Debug.Log($"アイテム名: {item.Name}, 値: {item.Value}");
        }
    }

    private void Start()
    {
        // ラムダ式を使って、値が10以上のアイテムを検索する
        List<Item> expensiveItems = FilterItems(item => item.Value >= 10);
        ShowItems("値が10以上のアイテム:", expensiveItems);

        // ラムダ式を使って、名前が "A" で始まるアイテムを検索する
        List<Item> itemsStartingWithA = FilterItems(item => item.Name.StartsWith("A"));
        ShowItems("名前がAで始まるアイテム:", itemsStartingWithA);
    }
}

 このコード例では、ItemConditionデリゲートとFilterItems高階関数を使用して、
Startメソッド内で、FilterItemsメソッドを使って異なる条件でアイテムをフィルタリングし、その結果を表示しています。
まず、値が10以上のアイテムを検索し、次に名前が "A" で始まるアイテムを検索しています。



 まず、Itemクラスに対する条件を表すデリゲートItemConditionを定義します。

public delegate bool ItemCondition(Item item);

 次に、ItemConditionデリゲートを引数にとる高階関数FilterItemsを作成します。
このメソッドは、指定された条件を満たすアイテムを含む新しいリストを返します。

private List<Item> FilterItems(ItemCondition condition)
{
    return items.Where(condition).ToList();
}

 この condition 引数にはラムダ式で作成された匿名メソッドが代入されています。

 そのため、Start メソッドで実行している下記のラムダ式は、デリゲート ItemCondition に代入されます。

List<Item> expensiveItems = FilterItems(item => item.Value >= 10); 

 ラムダ式は、FilterItems メソッドの引数の item => item.Value >= 10 の部分です。
Item 型の item を引数にとり、item.Value >= 10 を評価して真偽値を返すメソッドを表現しています。
そしてこの匿名メソッドによる内容が、FilterItems 高階関数に用意されているデリゲート ItemCondition 渡されています。

 FilterItems メソッド内で items.Where(condition).ToList(); を実行する際に、
このラムダ式(今回であれば item => item.Value >= 10)が代入された ItemCondition デリゲート condition が評価されます。

private List<Item> FilterItems(ItemCondition condition)  // condition の値は item => item.Value >= 10 
{
    return items.Where(condition).ToList();  // Where(condition) メソッドは、Where(item => item.Value >= 10) を行っている
}

 つまり、フィルター処理を行う際の条件部分に「デリゲートとラムダ式」を活用することで、
フィルタリングする条件がその都度変化するようになっています。こうすることでフィルタリング用のメソッドを複数用意する必要がありません

 そのため、より抽象化した処理を作成していくことが出来ます。



 このように高階関数を使って実装を行うことで、特定のアイテムを検索する、という処理を効率的・抽象的な形で実装していくことが出来ます。
コードがより柔軟で再利用可能になりますし、異なる条件を簡単に追加・変更することができます。