LinqのAnyメソッド使い方と範囲指定できるように機能拡張を行います。
Anyメソッドとは
簡単な説明
まず初めにAnyメソッドの説明です。
AnyメソッドはIEnumerableの拡張メソッド(=Linq)として定義されていて指定した配列やリストに中身があるかどうかを判定できます。
// Anyの宣言(1):中身があるかどうか判定できる public static bool Any<TSource>(this IEnumerable<TSource> source); // Anyの宣言(2):predicateで指定した条件が中身に存在するか確認できる public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
Anyメソッドの使い方
使い方は以下の通りです。
// Anyの宣言(1)の使い方 public static bool Any<TSource>(this IEnumerable<TSource> source); public void Foo() { // リストに中身があるかどうか判定する var list_1 = new List<int>(); bool result_1 list_1.Any(); // > result_1 = false : 空のリストの場合 false var list_2 = new List<int>() { 0, 1, 2, 3, 4, 5, 6 }; bool result_2 = list_2.Any(); // > result_2 = true : 中身があれば true } // Anyの宣言(2):の使い方 public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate); public void Foo() { // リストに条件に一致する中身があるかどうか判定する var list_1 = new List<int>(); bool result_1 list_1.Any(p => p > 5 /*5以上の要素があるか?*/); // > result_1 = false : 条件を指定しても空なので false var list_2 = new List<int>() { 0, 1, 2, 3, 4, 5, 6 }; bool result_2 = list_2.Any(p => p > 5 /*5以上の要素があるか?*/); // > result_2 = true : 5, 6が条件に一致するため true, p > 10 とすれば false }
サンプルではList
更に、IEnumerable
Anyメソッドは使用頻度(低)
余談ですがAnyメソッドは単体では使う機会はほぼ無いです。要素があるかどうかは「Count」や「Length」で確認できます。配列とListで操作を統一できるメリットはありますが、内部でイテレータを使用しているため速度がCountやLengthよりかなり遅いです。
それに条件を指定するほうも、同じLinqでContainsと機能がほぼ被っているのでメソッド名から意図がくみ取りやすいContainsを使用します。最近見かけませんが、昔に Linq to Object or SQL という構文中で割と使っていましたが最近ほぼ使わないですね。
Anyに範囲を指定するように拡張する
タイトルの件ですが、そんなAnyメソッドですが利用性を向上するために範囲指定できるようにしたいと思います。
EnumerableExtensionクラス
以下の通りEnumerableExtensionクラスを作成してIEnumerable
// IEnumerableの拡張メソッドを定義するクラス public static class EnumerableExtension { // (1) 要素の開始位置を指定して要素の有無を確認します。 public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, int startIndex) { int len = source.Count(); if (startIndex < 0 || startIndex > len) throw new ArgumentOutOfRangeException(nameof(startIndex), $"inde={startIndex}"); int i = 0; foreach (var item in source) { if (i++ < startIndex) continue; if (predicate(item)) return true; } return false; } // (2) 要素の対象範囲を指定して要素の有無を確認します。 public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, int startIndex, int endIndex) { int len = source.Count(); if (startIndex < 0 || startIndex > len) throw new ArgumentOutOfRangeException(nameof(startIndex), $"nameof(startIndex)={startIndex}"); if (endIndex < 0 || endIndex > len) throw new ArgumentOutOfRangeException(nameof(endIndex), $"nameof(endIndex)={endIndex}"); int i = 0; foreach (var item in source) { if (i > endIndex) return false; if (i++ < startIndex) continue; if (predicate(item)) return true; } return false; } }
指定できる範囲の指定は配列の要素番号で行います。
使い方
上記メソッドの使い方はそれぞれ以下の通りです。
拡張メソッドとして作成したためAnyと同じようにListに対してメソッド呼び出しで処理を記述できます。
public void Foo(params string[] args) { // (1) 要素の開始位置を指定して要素の有無を確認する var list = new List<int>() { 0, 1, 2, 3, 4, 5, 6 }; bool result_1 = list.Any(p => p > 5, 3); // リストの3つ目以降に5以上の値があるか? Console.WriteLine(result_1); // result_1 = true : p > 10 等にすると存在しないのでfalse // (2) 要素の対象範囲を指定して要素の有無を確認する bool result_2 = list.Any(p => p == 3, 2, 3); // リストの2つ目 ~ 4つ目の範囲内に3が存在するか? Console.WriteLine(result_2); // result_2 = true : p == 10 等にすると存在しないのでfalse }
【参考】Anyメソッドの実装
.NET(Framework)のソースコードはReferencesourceというサイトで中身が確認できます。
今回紹介したAnyメソッドの実装は以下の通りです。
https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,8788153112b7ffd0
public static bool Any<TSource>(this IEnumerable<TSource> source) { if (source == null) throw Error.ArgumentNull("source"); using (IEnumerator<TSource> e = source.GetEnumerator()) { if (e.MoveNext()) return true; } return false; } public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { if (source == null) throw Error.ArgumentNull("source"); if (predicate == null) throw Error.ArgumentNull("predicate"); foreach (TSource element in source) { if (predicate(element)) return true; } return false; }
見ればわかると思いますが、Any()はイテレータで最初の要素が取れればtrueで要素があると判定しています。
あんまり効率が良くなさそうです。
Any(predicate)は最初から最後までforeachでループして要素があるか確認してます。IEnumerableは長さが無限の場合が稀にあるので安易に実行すると処理が戻ってこない可能性があります。まぁ滅多に無いので頭の片隅に置いておくくらいで大丈夫です。