前回はArray.IndexOfを使って、配列の中から「この値と一致する要素」の位置を検索しました。今回は、同じく位置(インデックス)を返す検索メソッドであるArray.FindIndexを取り上げます。
IndexOfが「AAAという値はどこにあるか」というように、ピンポイントの値で検索するのに対して、FindIndexは「4文字のものはどこにあるか」というように、条件をラムダ式で指定して検索します。値そのものではなく条件で探せるという点が、今回のポイントです。
ボタンの準備
これまでのレクチャーと同じように、フォームに新しいボタンを1つ追加します。ボタンのTextは「FindIndexでの検索」としておきます。ボタンをダブルクリックして、クリックイベント(button7_Click)を生成します。
図1 フォームに「FindIndexでの検索」ボタンを追加する
生成されたイベントハンドラーの先頭には、何をするコードなのかがわかるようにコメントを書いておきます。
リスト1 生成したイベントハンドラーにコメントを書く
private void button7_Click(object sender, EventArgs e)
{
//検索_FindIndex
//ラムダ式(条件)で検索
}
検索対象のデータを作る
まずは検索対象になるデータを作ります。今回は文字列の配列にします。文字数がばらばらになるように、AAA、BBBB、CCC、D、EEEE、ABCDEという6つの値を入れておきます。
リスト2 検索対象となるstringの配列
//検索_FindIndex
//ラムダ式(条件)で検索
var data = new[]
{ "AAA", "BBBB", "CCC", "D", "EEEE", "ABCDE" };
図2 文字数の異なる6つの文字列を配列に入れる
new[]と書いているので、要素の型は右辺の値から型推論されます。すべて文字列なので、dataはstring[](stringの配列)になります。それぞれの文字数は、3文字、4文字、3文字、1文字、4文字、5文字です。
4文字のものを検索する
このデータの中から、たとえば「4文字のもの」を検索したいとします。「BBBB」という値がわかっているならIndexOfで探せますが、「4文字のもの」という条件ではIndexOfは使えません。こういう場合に使うのがFindIndexです。
図3 今回の検索条件は「4文字のもの」
Array.に続けて「fin」と入力すると、インテリセンスにFind、FindAll、FindIndex、FindLast、FindLastIndexといったFind系のメソッドが並びます。今回はこの中のFindIndexを選択します。
図4 Arrayクラスに用意されているFind系のメソッド
FindIndexの第1引数には、検索対象のデータを渡します。ここでは先ほど作ったdataです。そして第2引数はPredicate<T> matchになっています。ここに条件を渡すと、その結果がboolで返ってくる、という仕組みです。
図5 第2引数はPredicate<T> matchになっている
Predicate<T>とは
Predicate<T>は、何かしらのT型の値を渡したらboolを返してくる、というデリゲートです。.NETでは次のように定義されています。
リスト3 Predicate<T>の定義(参考)
//T型の値を1つ受け取って、boolを返す public delegate bool Predicate<in T>(T obj);
つまり、条件を書いてあげればtrueかfalseが返る、ということです。第2引数がこのPredicate<T>なので、ここにはラムダ式を書くことができます。
今回のデータは要素の1個1個がstringです。その1個をxだとして、「xのLengthが4のもの」という条件にします。
図6 ラムダ式のxは、配列の要素1個分(string)を表す
結果はresult1という変数で受け取ります。
リスト4 FindIndexで4文字のものを検索する
var data = new[]
{ "AAA", "BBBB", "CCC", "D", "EEEE", "ABCDE" };
var result1 = Array.FindIndex(data, x => x.Length == 4);
x => x.Length == 4の部分がラムダ式です。左側のxが受け取る値(配列の要素1個)、右側のx.Length == 4がboolを返す条件です。なお、このラムダ式の書き方の意味がわからない方は、別のコースにラムダ式のコースがありますので、そちらを参照してください。
実行して結果を確認する
メソッドの最後にブレークポイントを置いて実行し、ボタンを押します。result1にマウスカーソルを合わせると、値が1になっていることが確認できます。
図7 result1は1になっている
FindIndexは、データの中身の1個1個に対して条件を当てはめていきます。それぞれtrueかfalseが返るので、その中で最初にtrueになった要素のインデックスが返ってくる、という動きです。今回の条件Length == 4を各要素に当てはめると、次のようになります。
リスト5 各要素に対する条件の判定結果
//index:0 "AAA" → Length は 3 → false //index:1 "BBBB" → Length は 4 → true ← 最初に見つかる //index:2 "CCC" → Length は 3 → false //index:3 "D" → Length は 1 → false //index:4 "EEEE" → Length は 4 → true //index:5 "ABCDE" → Length は 5 → false
最初に見つかるのは「BBBB」です。配列のインデックスは0始まりなので、「BBBB」の位置は1になります。
図8 最初に見つかる4文字の要素は「BBBB」(インデックス1)
4文字の要素は「EEEE」(インデックス4)にもありますが、FindIndexが返すのはあくまで最初に見つかったインデックスです。そのため、今回の結果は1になります。わかりやすいように、コードにもコメントを残しておきます。
リスト6 結果をコメントで残しておく
//4文字のモノ //最初に見つかるindex:1となる var result1 = Array.FindIndex(data, x => x.Length == 4);
ラムダ式の代わりにメソッドを渡す
ここまではラムダ式を使いましたが、第2引数はPredicate<T>ですので、「T型を受け取ってboolを返すもの」であれば渡すことができます。今回はデータがstringの配列なので、Tはstringになります。FindIndexにマウスカーソルを合わせると、Predicate<string> matchと表示されていることが確認できます。
図9 stringの配列なので、第2引数はPredicate<string>になる
つまり、stringを受け取ってboolを返す関数を指定すればよい、ということです。ラムダ式に慣れていない方のために、同じ条件をメソッドとして外出しするとどうなるかを見てみます。
button7_Clickの外に、stringを受け取ってboolを返すメソッドを作ります。名前はIs4mojiとしておきます。
リスト7 stringを受け取ってboolを返すメソッド
private bool Is4moji(string value)
{
return value.Length == 4;
}
図10 4文字かどうかを判定するIs4mojiメソッドを作成する
value.Length == 4は、valueの文字数が4であればtrue、そうでなければfalseになります。これでboolが返るメソッドになりました。
stringを受け取ってboolを返すメソッドがあれば、このメソッドをそのままFindIndexに渡すことができます。ラムダ式になっていたところを、Is4mojiというメソッド名に置き換えます。
リスト8 ラムダ式の代わりにメソッド名を渡す
//Predicate<T>なので //stringを受け取って、boolを返す関数を指定する var result2 = Array.FindIndex(data, Is4moji);
図11 第2引数にメソッド名のIs4mojiを指定する
ここで注意したいのは、Is4moji()のようにカッコを付けて呼び出すのではなく、メソッド名だけを渡すという点です。メソッドを実行した結果ではなく、メソッドそのものをFindIndexに渡しています。FindIndexの内部で、配列の要素1個1個に対してこのIs4mojiが呼び出されます。
実行して確認すると、result2もラムダ式のときと同じく1が返ってきます。
図12 メソッドを渡した場合もresult2は1になる
ラムダ式とメソッド、どちらのやり方でも結果は同じです。ただ、条件を1行で書けるラムダ式のほうが実務ではよく使われますので、ラムダ式がわからない方は、ラムダ式のコースを見ていただくことをおすすめします。
条件に一致する要素が存在しない場合
最後に、条件に一致する要素が存在しない場合の動きを確認します。たとえば条件を6文字にしてみます。今回のデータには6文字のものはありません。
リスト9 存在しない条件(6文字)で検索する
var result1b = Array.FindIndex(data, x => x.Length == 6);
実行してresult1bを確認すると、-1になっています。
図13 一致する要素がない場合、result1bは-1になる
条件に一致する要素が存在しなかった場合は、-1が返ってきます。これは前回のIndexOfと同じです。基本的に、インデックスが返ってくるメソッドは、見つからなかったら-1が返ると覚えておいてください。
そのため、実際のコードでFindIndexの結果をインデックスとして使う場合は、次のように-1かどうかを確認してから使うようにします。-1のままdata[index]のようにアクセスすると、IndexOutOfRangeExceptionが発生してしまいます。
リスト10 見つかったかどうかを判定してから使う(参考)
var index = Array.FindIndex(data, x => x.Length == 6);
if (index < 0)
{
//見つからなかった場合の処理
return;
}
//見つかった場合だけ、インデックスを使ってアクセスする
var value = data[index];
今回のコード全体
今回作成したコードの全体は次のとおりです。
リスト11 FindIndexでの検索(全体)
private void button7_Click(object sender, EventArgs e)
{
//検索_FindIndex
//ラムダ式(条件)で検索
var data = new[]
{ "AAA", "BBBB", "CCC", "D", "EEEE", "ABCDE" };
//4文字のモノ
//最初に見つかるindex:1となる
var result1 = Array.FindIndex(data, x => x.Length == 4);
//Predicate<T>なので
//stringを受け取って、boolを返す関数を指定する
var result2 = Array.FindIndex(data, Is4moji);
var result1b = Array.FindIndex(data, x => x.Length == 6);
}
private bool Is4moji(string value)
{
return value.Length == 4;
}
まとめ
Array.FindIndexは、第1引数に検索対象の配列、第2引数に条件(Predicate<T>)を指定して検索するメソッドです。IndexOfのようにピンポイントの値で探すのではなく、ラムダ式で書いた条件に一致する要素を探せます。
条件は配列の要素1個1個に対して判定され、最初にtrueになった要素のインデックスが返ってきます。第2引数にはラムダ式だけでなく、同じ形(T型を受け取ってboolを返す)のメソッドを渡すこともできます。そして、条件に一致する要素が1つもなければ、IndexOfと同じく-1が返ってきます。
値がわかっているならIndexOf、条件で探したいならFindIndex、という形で使い分けてみてください。
■非公開コース「C#14新機能」プレゼント:
非公開コース「C#14新機能」(80分)をご覧になりたい方は
こちらからURLとパスワードを発行していますので、ご覧になってみてください。
非公開コース「C#14新機能」を観る
A01_はじめに
A02_プロジェクトの作成
B01_配列とは
B02_配列の生成とアクセス
B03_生成と同時に値を設定する
B04_型推論による生成
B05_メソッドの引数などにする場合の注意点
B06_Length
B07_IndexOfでの検索
B08_FindIndexでの検索
B09_Find
B10_Exists
B11_FindAllとFindLast
B12_誤ったコピー
B13_Array.Copy
B14_範囲指定のコピー
B15_Resize
C01_ArrayList
C02_List
C03_List 動的な要素の変更
C04_Listのコンストラクタ
C05_Listのコンストラクタ_Capacity
C06_ListTからArrayクラスのメソッドが呼ばれている
D01_コレクションインタフェースとは
D02_異なるコレクションクラスに互換性を持たせる
D03_インタフェースの階層構造
D04_クライアントができる操作を制限させる
D05_クライアントができる操作を制限させる_後半
D06_Enumerableの拡張メソッドに関して
D07_ReadOnly系のコレクションインタフェース
D08_AsReadOnly
D09_ToListでコピーする
D10_ListTはprivateで使う
■非公開コース「C#14新機能」プレゼント:
非公開コース「C#14新機能」(80分)をご覧になりたい方は
こちらからURLとパスワードを発行していますので、ご覧になってみてください。
非公開コース「C#14新機能」を観る












