C#のLINQの関数であるElementAt()
、ElementAtOrDefault()
の使い方についてです。
配列やリストといったシーケンスの指定したインデックスの要素を取得することが出来ます。
この記事には.NET Framework 4.6.1を使用しています。
指定したインデックスの要素を取得してみよう
突然ですが、ここでくえすちょんだ。
Apples
という配列の変数があるとしよう。
この配列の4番目の要素を取得するにはどうすればいいだろうか?
では、そこのお嬢さん、お答えをどうぞ!
……正解です!
Apples[3]
と記述すればいいんですね。
コード的には1番目が 0 から始まるため、4番目の場合は 3 と記述するのが正しいですね。
配列やList
クラスの場合は、[]
によるアクセサが用意されているので、指定したインデックスの要素を取得するのはとっても簡単です。
では用意されていないシーケンス型のクラスの場合はどうすればいいでしょうか?
では、そこのジェントルマン、お答えをどうぞ!
……その通り!
LINQのElementAt()
を使えばいいのです。
IEnumerable<T>
を継承しているクラスなら、この力を使うことが出来ます。
public static TSource ElementAt<TSource>( this IEnumerable<TSource> source, int index );
Enumerable.ElementAt(TSource) メソッド (IEnumerable(TSource), Int32) (System.Linq)
では実演してみましょう。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { // 指定するインデックスの番号 private static readonly int SELECTED_INDEX = 4; static void Main( string[] args ) { // データ int[] numbers = new int[] { 1, 2, 3, 5, 7, 11 }; int result = numbers.ElementAt( SELECTED_INDEX ); // 結果発表 System.Console.WriteLine( "データ:{0}", numbers.Text() ); System.Console.WriteLine( "指定したインデックス:{0}", SELECTED_INDEX ); System.Console.WriteLine( "結果:{0}", result ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なシーケンスのテキスト取得処理 /// </summary> public static string Text<TSource>( this IEnumerable<TSource> i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
データ:[1], [2], [3], [5], [7], [11],
指定したインデックス:4
結果:7
[]
を使った場合と同じ結果になります。
今回の場合は配列に対して使っているので、[]
アクセサを使った方が早いと思うので、ElementAt()
を使う旨みはほとんど無いんですが、自作でシーケンスクラスを作った場合などには重宝しそうです。
不正なインデックスのとき、デフォルト値を返すElementAtOrDefault
さて、先ほどのElementAt()
をこのように使ったらどうなるでしょうか。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { // 指定するインデックスの番号 // (要素数を超える数値を指定しちゃう) private static readonly int SELECTED_INDEX = 10; static void Main( string[] args ) { // データ int[] numbers = new int[] { 1, 2, 3, 5, 7, 11 }; int result = 0; try { result = numbers.ElementAt( SELECTED_INDEX ); } catch( System.Exception i_exception ) { System.Console.WriteLine( "例外だよ:{0}", i_exception ); // 入力待ち用 System.Console.ReadKey(); return; } // 結果発表 System.Console.WriteLine( "データ:{0}", numbers.Text() ); System.Console.WriteLine( "指定したインデックス:{0}", SELECTED_INDEX ); System.Console.WriteLine( "結果:{0}", result ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なシーケンスのテキスト取得処理 /// </summary> public static string Text<TSource>( this IEnumerable<TSource> i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
例外だよ:System.ArgumentOutOfRangeException: インデックスが範囲を超えています。
負でない値で、シーケンスのサイズよりも小さくなければなりません。
[]
アクセサを使う際に一度は経験があるであろう、アウトオブレンジの例外の発生。
例外が発生するC#ならまだしも、C++などでこれをやると、大惨事になります。
ElementAt()
でも同様の例外が発生します。
ですが、ElementAt()
のお友達にElementAtOrDefault()
というものがいます。
public static TSource ElementAtOrDefault<TSource>( this IEnumerable<TSource> source, int index );
Enumerable.ElementAtOrDefault(TSource) メソッド (IEnumerable(TSource), Int32) (System.Linq)
先ほどのコードのElementAt()
の部分をElementAtOrDefault()
に差し替えてみましょう。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { // 指定するインデックスの番号 // (要素数を超える数値を指定しちゃう) private static readonly int SELECTED_INDEX = 10; static void Main( string[] args ) { // データ int[] numbers = new int[] { 1, 2, 3, 5, 7, 11 }; int result = 0; try { result = numbers.ElementAtOrDefault( SELECTED_INDEX ); } catch( System.Exception i_exception ) { System.Console.WriteLine( "例外だよ:{0}", i_exception ); // 入力待ち用 System.Console.ReadKey(); return; } // 結果発表 System.Console.WriteLine( "データ:{0}", numbers.Text() ); System.Console.WriteLine( "指定したインデックス:{0}", SELECTED_INDEX ); System.Console.WriteLine( "結果:{0}", result ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なシーケンスのテキスト取得処理 /// </summary> public static string Text<TSource>( this IEnumerable<TSource> i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
データ:[1], [2], [3], [5], [7], [11],
指定したインデックス:10
結果:0
なんと例外が発生しない!
ElementAtOrDefault()
ではこのような不正なインデックスが渡された場合は、型のデフォルトの値が返ってきます。
そのため、このような書き方でも例外は発生しません。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { // 指定するインデックスの番号 // (負数を指定しちゃう!) private static readonly int SELECTED_INDEX = -1; static void Main( string[] args ) { // データ int[] numbers = new int[] { 1, 2, 3, 5, 7, 11 }; int result = 0; try { result = numbers.ElementAtOrDefault( SELECTED_INDEX ); } catch( System.Exception i_exception ) { System.Console.WriteLine( "例外だよ:{0}", i_exception ); // 入力待ち用 System.Console.ReadKey(); return; } // 結果発表 System.Console.WriteLine( "データ:{0}", numbers.Text() ); System.Console.WriteLine( "指定したインデックス:{0}", SELECTED_INDEX ); System.Console.WriteLine( "結果:{0}", result ); // 入力待ち用 System.Console.ReadKey(); } /// <summary> /// 簡易的なシーケンスのテキスト取得処理 /// </summary> public static string Text<TSource>( this IEnumerable<TSource> i_source ) { string text = string.Empty; foreach( var value in i_source ) { text += string.Format( "[{0}], ", value ); } return text; } } // class Program
データ:[1], [2], [3], [5], [7], [11],
指定したインデックス:-1
結果:0
例外が発生しないことは大変助かるのですが、必ずしも良いかと言われればそうではありません。
今回の場合でしたら、int
型のデフォルトの値である 0 が返ってきています。
これは「指定したインデックスの要素に0が入っていた」のか「要素が見つからなかったため、デフォルトの値である 0 が返ってきた」のかがわからないという欠点があるのです。
その点に気をつけて、ElementAt()
とElementAtOrDefault()
を七兆回使ってみてください。
LINQのリンク
LINQ一覧
www.urablog.xyzFirst, FirstOrDefault
先頭の要素を取得したい!
www.urablog.xyzLast, LastOrDefault
最後尾の要素を取得したい!
www.urablog.xyzSingle, SingleOrDefault
一つだけの要素を取得したい!
www.urablog.xyz