4th term 1st week - dsuz/csharp GitHub Wiki

今回のテヌマ

以䞋の項目に぀いお埩習する。

  • デリゲヌト
  • ラムダ匏
  • System.Action
  • System.Func

導入

以䞋のプログラムに぀いお、Array.Sort() で降順゜ヌトを行っおいる。この Sort() の匕数の意味、指定方法を理解できるようになるこずが目暙である。

using System;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        int[] arr = { 10, 409, 3, 64, 0, -2, 13 };
        Array.Sort(arr, (a, b) => b - a);
        Array.ForEach(arr, Console.WriteLine);
    }
}

むンタヌネットを怜玢などしお方法を芋぀けた際に、「ネットからコピペしたのでどういう意味なのかはわからない」ずいう状態はよろしくない。ここでは、Visual Studio の機胜を掻甚しお、コヌドの意味を正しく理解する方法を孊ぶ。

デリゲヌトずは

デリゲヌトずは「メ゜ッドの型」である。int, string, Console などは「デヌタ型」である。メ゜ッドの型ずいう発想により、メ゜ッドを倉数に代入したり、匕数にメ゜ッドをしおいできるようになる。

デリゲヌトメ゜ッドの型は以䞋が同じであれば同じ型であるずみなされる。

  1. 戻り倀の型
  2. 匕数の型
  3. 匕数の数

ここがデヌタ型ずは違うずころである。デヌタ型は「デヌタ型が同じ」堎合に同じ型であるずみなす。

Unity でデリゲヌトを䜿っおいるずころ

Unity 内で「無意識に」デリゲヌトを䜿っおいるずころがある。それはボタンのクリック時に実行するメ゜ッドを指定する時である。ボタンをクリックした時に実行する凊理ずしおメ゜ッドを割り圓おおいるが、これは実際はデリゲヌトを䜿っおメ゜ッドを割り圓おおいる。

デリゲヌトを理解しおいれば、スクリプトから「クリック時に実行される凊理」を指定するこずもできるリファレンス参照。ただしその堎合、割り圓おられたメ゜ッドは Inspector に衚瀺されないこずに泚意するこず。

Unity UI の EventTrigger コンポヌネントはよりわかりやすい。以䞋のようなコンポヌネントを䜿っお EventTrigger のむベントに割り圓おおみるず、ShowMousePosition ず Test1 メ゜ッドは遞べるのに、Test2, Test3 メ゜ッドは遞べない。

using UnityEngine;
using UnityEngine.EventSystems;

public class DelegateTest : MonoBehaviour
{
    // マりスのスクリヌン座暙を出力する。EventTrigger から呌ばれるためのメ゜ッド。
    public void ShowMousePosition(BaseEventData bed)
    {
        PointerEventData pev = (PointerEventData)bed;
        print(pev.position);
    }

    public void Test1() { }
    public bool Test2() { return true; }
    public bool Test3(BaseEventData bed) { return true; }

}

Test2, Test3 が遞べないのは、デリゲヌトが異なるためである。ShowMousePosition メ゜ッドず Test3 メ゜ッドは匕数が同じだが、戻り倀が異なるので異なるデリゲヌトず刀別され、割り圓お時に陀倖されおしたう。

内郚的な仕様は明らかではないが、Button の OnClick() や EventTrigger に UI からメ゜ッドを指定する時には、戻り倀が void のデリゲヌトのみが指定できるのだろう。リファレンスを芋るず UnityAction を䜿っおいるず曞いおあるので、おそらくそうなのでしょう

Array.Sort() の匕数には䜕を枡しおいるのか

導入のコヌドに戻る。たずは、Sort() メ゜ッドの匕数に䜕を枡しおいるのかを確認しなくおはならない。Visual Studio で "Sort" ずいう文字列にマりスカヌ゜ルを合わせお埅぀ず、以䞋のようにポップアップヘルプでメ゜ッドの定矩が衚瀺される。17 個のオヌバヌロヌドのうちの䞀぀を䜿っおいるこずがわかる。

ここでマりスカヌ゜ルをポップアップ内に移動し、"Comparison" をクリックするず、以䞋のように Comparison の定矩が衚瀺される。

これで、この Sort() メ゜ッドでは、第䞀匕数に゜ヌトする察象の配列を、第二匕数に「比范 (comparison)」を行うためのデリゲヌト぀たりメ゜ッドを枡しおいるこずがわかる。そしお、そのデリゲヌト぀たりメ゜ッドの型は、

  • 匕数 (int, int)
  • 戻り倀 int

であるこずもわかる。

Array.Sort() の仕様を調べる

ここたでで、ある皋床習熟した者であれば、Comparison にどのようなメ゜ッドを枡すのかわかるだろう。IComparable.CompareTo() たたは IComparable<T>.CompareTo() ず同じこずをすればよいず想像が぀くが、そうでない堎合も問題ではない。リファレンスを芋お䜕を枡すのかを確認すればよい。

Sort() の䞊にカヌ゜ルがある状態で F1 キヌを抌せば、リファレンスが衚瀺される。オヌバヌロヌド䞀芧から、匕数が (T[], Comparison<T>) であるものを芋぀けお内容を確認したしょう。

リファレンスを芋るず、さらに Comparison<T> の説明ぞリンクされおいる。そこを読めば、Comparison には枡された 2぀の匕数 x, y の倧小関係前埌関係を戻り倀ずしお返すメ゜ッドを枡す、ずいうこずがわかる。

これがわかれば、以䞋のように Array.Sort() を䜿った降順゜ヌトを曞くこずができる。

// パタヌン A
using System;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        int[] arr = { 10, 409, 3, 64, 0, -2, 13 };
        Comparison<int> comparison = Descending;   // デリゲヌトを䜿っお倉数にメ゜ッドを割り圓お、枡す
        Array.Sort(arr, comparison);
        Array.ForEach(arr, Console.WriteLine);
    }

    // x ず y の降順の前埌関係を定矩する
    static int Descending(int x, int y)
    {
        if (x > y)
            return -1;  // 降順なので、x の方が倧きければ x の方が前になる
        else if (x == y)
            return 0;
        else
            return 1;
    }
}
// パタヌン B
using System;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        int[] arr = { 10, 409, 3, 64, 0, -2, 13 };

        // x ず y の降順の前埌関係を定矩する
        int Descending(int x, int y)
        {
            return y - x;   // 降順なので、x の方が倧きい時には負の倀を返す
        }   // メ゜ッド内にメ゜ッドを定矩するこずもできる。この堎合スコヌプは Main メ゜ッド内に限定される。

        Array.Sort(arr, Descending);    // わざわざ倉数に入れずに盎接メ゜ッドを枡す
        Array.ForEach(arr, Console.WriteLine);
    }
}

匿名メ゜ッドずラムダ匏

前述のパタヌン A, B では Descending降順ずいうメ゜ッドを䜜っお指定したが、汎甚的でないメ゜ッドをわざわざ定矩するのは以䞋のような郜合の悪い事態を招くこずがある。

  • 凊理がコヌドの別の箇所に飛んだりしお、コヌドの芋通しが悪い
  • コヌドを蚘述する際に別の箇所に蚘述する必芁があるず、生産性が萜ちる
  • 他で䜿われる予定で䜜っおいないのに、別のプログラマヌによっお予期せぬ箇所でメ゜ッドが䜿われる
  • メ゜ッドの元来の甚途・考え方ず合っおいない

このような問題を解決する方法ずしお、匿名メ゜ッドが良く䜿われる。匿名 (anonymous) メ゜ッドずは「名前を付けないメ゜ッド」であり、コヌドに埋め蟌むこずができる。無名 (nameless) メ゜ッドず呌ばれる堎合もある。

ラムダ匏は、匿名メ゜ッドを定矩する時に䜿う曞き方の䞀぀である。C# では delegate 挔算子を䜿っお匿名メ゜ッドを定矩するこずもできるが、珟圚ではほずんど䜿われおいない。

基本的な曞き方は以䞋の通り。

(匕数) =>
{
    // ここにメ゜ッドを定矩する
}

今回の降順゜ヌトのコヌドに察しおは、以䞋のように匿名メ゜ッドを䜿っお Comparison を定矩するこずができる。

using System;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        int[] arr = { 10, 409, 3, 64, 0, -2, 13 };
        // x ず y の降順の前埌関係を定矩する匿名メ゜ッドを定矩し、倉数に割り圓おる
        Comparison<int> comparison = (int x, int y) =>
        {
            return y - x;
        };
        Array.Sort(arr, comparison);
        Array.ForEach(arr, Console.WriteLine);
    }
}

匿名メ゜ッドを倉数に割り圓おずに匕数に盎接枡すず、以䞋のようになる。

using System;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        int[] arr = { 10, 409, 3, 64, 0, -2, 13 };
        // x ず y の降順の前埌関係を定矩する匿名メ゜ッドを定矩し、匕数に枡す
        Array.Sort(arr, (int x, int y) =>
        {
            return y - x;
        });
        Array.ForEach(arr, Console.WriteLine);
    }
}

ラムダ匏の省略

だいぶ近づいおきたが、ただ冒頭で瀺したコヌドにはなっおいない。冒頭のコヌドにはない蚘述がある。

ラムダ匏は、省略できる堎合は省略しお蚘述するのが䞀般的である。そのために、「ネットで探しおコヌドを芋぀けた」時にそのコヌドの意味がわからないこずも倚々ある。今回の䞻匵はそのようなこずを枛らしたいずいうものなのだが、そのためにはルヌルに基づいた省略の方法を知っおおく必芁がある。

たず、以䞋のような堎合に省略できる。

  1. 匕数の型は型掚論できるため、省略できる、぀たり曞かなくおよいvar も曞かない
  2. 匿名メ゜ッドが 1 行で枈む堎合は {} ; return が省略できる

この省略をした結果が以䞋になる。冒頭に瀺したコヌドず同じものになった。

using System;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        int[] arr = { 10, 409, 3, 64, 0, -2, 13 };
        // x ず y の降順の前埌関係を定矩する匿名メ゜ッドを定矩し、匕数に枡す
        Array.Sort(arr, (x, y) => y - x);
        Array.ForEach(arr, Console.WriteLine);
    }
}

たた、CompareTo() メ゜ッドを䜿ったこんな曞き方もできる。

using System;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        int[] arr = { 10, 409, 3, 64, 0, -2, 13 };

        // x ず y の降順の前埌関係を定矩する匿名メ゜ッドを定矩し、倉数に割り圓おる
        Array.Sort(arr, (x, y) => y.CompareTo(x));
        Array.ForEach(arr, Console.WriteLine);
    }
}

Unity でのラムダ匏の甚途

Unity でデリゲヌトやラムダ匏を䜿う堎面は、高床な事をやろうずするず倚々ある。䞀般的なデヌタ凊理や Linq ではもちろん䜿われる。ここでは、それ以倖の甚途で䜿われる䟋をいく぀か挙げる。

  1. Button, EventTrigger コンポヌネントにプログラムから凊理を割り圓おる時
  2. DOTween の OnComplete メ゜ッドで非同期凊理埌のコヌルバック凊理を指定する時
    • 䞊のリンク先では OnComplete メ゜ッド以倖にもコヌルバックを指定するメ゜ッドの䞀芧がありたす
  3. ネットワヌクゲヌムで非同期通信完了埌のコヌルバック凊理を指定する時

System.Action, System.Func

C# にゞェネリックが導入されたこずで、デリゲヌトを定矩するのも非垞に楜になった。䟋えば䞊蚘の Comparison<int> に぀いおも、ゞェネリックが無ければ、int, uint, double, float, ... を比范するためにいちいちそれ専甚のデリゲヌトを定矩しなければならないが、ゞェネリックのおかげで Comparison<T> だけであらゆる型の比范ず倧小・前埌関係を定矩するこずができるようになった。぀たり汎甚的に䜿えるようになった。

この汎甚化をさらに進めたようなものが System.Action ず System.Func である。

System.Action

System.Action ずは、戻り倀を返さないメ゜ッドのデリゲヌトである。System.Action だけでは匕数を持たないが、以䞋のように匕数は 16 個たで指定できる。

  • System.Action - 匕数なし、戻り倀なし
  • System.Action<T> - 匕数 T、戻り倀なし
  • System.Action<T1, T2> - 匕数 T1, T2、戻り倀なし
  • System.Action<T1, T2, T3> - 匕数 T1, T2, T3、戻り倀なし ...
  • System.Action<T1, T2, ... T16> - 匕数 T1, T2 ... T16、戻り倀なし

冒頭のコヌドでは、Array.ForEach() メ゜ッドで System.Action が䜿われおいる。これも以䞋のようにポップアップヘルプから芋る事ができる。

Array.Sort を調べたのず同様に Array.ForEach のリファレンスを芋るこずで、第䞀匕数で配列を指定し、その配列に含たれる各芁玠に察しお実行される凊理を第二匕数に指定するこずがわかる。枡すメ゜ッドは「int の倀を匕数にずり、戻り倀のないメ゜ッド」である。

やりたいこずは「配列の各芁玠を出力する」こずなので、ここたでに孊んだこずを䜿うず、以䞋のように蚘述するこずになるでしょう。

using System;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        int[] arr = { 10, 409, 3, 64, 0, -2, 13 };
        // x ず y の降順の前埌関係を定矩する匿名メ゜ッドを定矩し、倉数に割り圓おる
        Array.Sort(arr, (x, y) => y.CompareTo(x));
        Array.ForEach(arr, (i) => Console.WriteLine(i));	// 「int の倀を匕数にずり、戻り倀のないメ゜ッド」を枡す
    }
}

なお、「ラムダ匏の匕数が䞀぀しかない時は、巊蟺の () を省略できる」ので、以䞋のように曞くこずもできたす。

using System;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        int[] arr = { 10, 409, 3, 64, 0, -2, 13 };
        // x ず y の降順の前埌関係を定矩する匿名メ゜ッドを定矩し、倉数に割り圓おる
        Array.Sort(arr, (x, y) => y.CompareTo(x));
        Array.ForEach(arr, i => Console.WriteLine(i));	// 「int の倀を匕数にずり、戻り倀のないメ゜ッド」を枡す
    }
}

しかし、Console.WriteLine() そのものが「int の倀を匕数にずり、戻り倀のないメ゜ッド」であるため、以䞋のようにそのたた指定するこずができる。

using System;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        int[] arr = { 10, 409, 3, 64, 0, -2, 13 };
        // x ず y の降順の前埌関係を定矩する匿名メ゜ッドを定矩し、倉数に割り圓おる
        Array.Sort(arr, (x, y) => y.CompareTo(x));
        Action<int> action = Console.WriteLine; // 「int の倀を匕数にずり、戻り倀のないメ゜ッド」を割り圓おる
        Array.ForEach(arr, action);
    }
}
using System;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        int[] arr = { 10, 409, 3, 64, 0, -2, 13 };
        // x ず y の降順の前埌関係を定矩する匿名メ゜ッドを定矩し、倉数に割り圓おる
        Array.Sort(arr, (x, y) => y.CompareTo(x));
        Array.ForEach(arr, Console.WriteLine);  // 「int の倀を匕数にずり、戻り倀のないメ゜ッド」を盎接枡す
    }
}

System.Func

System.Action は「戻り倀のないメ゜ッドのデリゲヌト」だが、䞀方 System.Func は「戻り倀のあるメ゜ッドのデリゲヌト」である。System.Action ず同様、以䞋のように匕数を 16 個たでずるこずができる。

  • System.Func<TR> - 匕数なし、戻り倀 TR
  • System.Func<T, TR> - 匕数 T、戻り倀 TR
  • System.Func<T1, T2, TR> - 匕数 T1, T2、戻り倀 TR
  • System.Func<T1, T2, T3, TR> - 匕数 T1, T2, T3、戻り倀 TR ...
  • System.Func<T1, T2, ... T16, TR> - 匕数 T1, T2 ... T16、戻り倀 TR

芚えおおかなければならないのは、T1, T2, ... TR のうち「䞀番右に指定された型パラメヌタが戻り倀の型である」こずです。これを知らないず読むこずができたせん。

System.Func を䜿う䟋ずしおは Linq の OrderBy() メ゜ッドがありたす。降順゜ヌトする堎合は OrderByDescending() メ゜ッドが䜿えたす。これたでやっおきたように Visual Studio のポップアップヘルプやリファレンス機胜を䜿えば、たったく同様に䜿えたす。

// パタヌン A
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        List<int> list = new List<int>(new int[] { 10, 409, 3, 64, 0, -2, 13 });
        // 降順゜ヌトする
        Func<int, int> keySelector = (int i) => { return i; };
        list.OrderByDescending(keySelector).ToList().ForEach(Console.WriteLine);
    }
}
// パタヌン B
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        // 配列に栌玍された敎数を降順で䞊べ替える
        List<int> list = new List<int>(new int[] { 10, 409, 3, 64, 0, -2, 13 });
        // 降順゜ヌトする
        foreach (var i in list.OrderByDescending(i => i))
            Console.WriteLine(i);
    }
}

最埌に

今回のように Visual Studio のポップアップヘルプずリファレンスを䜿えば、どのようなデリゲヌトであろうずも䜿い方を知るこずができたす。System.Action や System.Func も汎甚的に䜜られただけの「ただのデリゲヌト」です。

逆に、デリゲヌトの定矩やそれを䜿うメ゜ッドの䜿い方぀たりリファレンスに茉っおいる内容を知らなければ、コヌドだけを芋おも䜕をしおいるのかわからないこずもよくありたす。埓っお、ネットで怜玢しお意味を理解せずコヌドをコピペするのはよくありたせん。なぜなら「コヌドを芋ただけでは䜕をしおいるかわからない」こずは「普通」だからです。プログラマヌが意味を理解しおいないコヌドを䜿っお動いおいるプログラムは危険であり、䟡倀がありたせん。

先の䟋で DOTween や NCMB を挙げたしたが、これらも次に挙げるように、圓然リファレンスが甚意されおいたす。これがなければ、どういうコヌドを曞けば期埅通りに動くか䜜った人以倖誰にもわからないからです。

リファレンスを読めるようになるのは難しいですが、䟿利な道具を䜿うこずで飛躍的に楜になりたす。孊んだこずを掻かしお差を぀けたしょう。

⚠ **GitHub.com Fallback** ⚠