C#のLINQの関数であるLast()
、LastOrDefault()
の使い方についてです。
配列やリストといったシーケンスの最後の要素を取得することが出来ます。
取得する要素に条件を指定すれば、List
クラスのFindLast()
のように使用することもできます。
この記事には.NET Framework 4.6.1を使用しています。
最後の要素を取得する
配列やリストなどシーケンスにおいて、最後の要素を取得したいとき、どうしています?
ん、そんなときなどない?
そ、そっかー……。
まぁ、でもそのうちあるかもしれませんよ。
LINQの機能であるLast()
を使うことで、IEnumerable
を継承したシーケンスならば、簡単に最後の要素が取得することできるのです。
public static TSource Last<TSource>( this IEnumerable<TSource> source );
Enumerable.Last(TSource) メソッド (IEnumerable(TSource)) (System.Linq)
では使ってみましょう。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // データ int[] numbers = new int[] { 1, 2, 3, 5, 7, 11 }; int result = numbers.Last(); // 結果発表 System.Console.WriteLine( "データ:{0}", numbers.Text() ); 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],
結果:11
簡単に出来て便利ですね。
条件に合った最後の要素を取得する
先ほどは、配列の最後の要素を取得しましたが、Last()
は取得条件を指定することもできます。
public static TSource Last<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate );
条件を指定した場合は、条件に合った最後の要素を取得できるのです。
……。
この「条件に合った最後の要素」というのは日本語として難しいですね。
「最後から先頭に向かって要素を調べていき、最初に合った要素を取得する」と言ったほうがわかりやすかったです。
とりあえず使ってみましょう。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // データ int[] numbers = new int[] { 1, 2, 3, 5, 7, 11 }; // 10未満の要素を探すよ! int result = numbers.Last( value => value < 10 ); // 結果発表 System.Console.WriteLine( "データ:{0}", numbers.Text() ); 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],
結果:7
条件はラムダ式で書けるので、簡単な条件ならサラッと書けます。
では、自作したクラスデータの配列から、特定の条件を取得する処理を書いてみましょう。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class Parameter { public int ID { get; set; } public string Name { get; set; } public override string ToString() { return string.Format( "ID:{0}, Name:{1}", ID, Name ); } } static void Main( string[] args ) { // データ Parameter[] parameters = new Parameter[] { new Parameter() { ID = 5, Name = "正一郎" }, new Parameter() { ID = 13, Name = "清次郎" }, new Parameter() { ID = 25, Name = "誠三郎" }, new Parameter() { ID = 42, Name = "征史郎" }, }; // IDが20未満の要素を取得するよ! Parameter result = parameters.Last( value => value.ID < 20 ); // 結果発表 System.Console.WriteLine( "データ:{0}", parameters.Text() ); 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
データ:[ID:5, Name:正一郎], [ID:13, Name:清次郎], [ID:25, Name:誠三郎], [ID:42, Name:征史郎],
結果:ID:13, Name:清次郎
例外が発生するとき
Last()
はこのような時に使用すると例外が発生してしまいます。
リストが空のとき
配列やリストの要素が空のときにLast()
を使うと例外が発生します。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // データ(あえて空のデータにしてみました) int[] numbers = new int[] {}; int result = 0; try { result = numbers.Last(); } 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}", 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.InvalidOperationException: シーケンスに要素が含まれていません
条件に合う要素が見つからないとき
指定した条件の要素が一つもないときも例外が発生します。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // データ int[] numbers = new int[] { 1, 2, 3, 5, 7, 11 }; int result = 0; try { // 20より大きい数値を探すよ! result = numbers.Last( value => value > 20 ); } 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}", 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.InvalidOperationException: シーケンスに、一致する要素は含まれてません
例外を出したくないならLastOrDefault
try
~catch
を書けば、Last()
を使って例外が発生した場合でも対応できます。
でも、そこら中にtry
~catch
を書くのは面倒くさいなぁ。
そんな状況にはLastOrDefault()
というものがあります。
上記の例外が出るパターンの場合には、型のデフォルトの値が返ってきます。
public static TSource LastOrDefault<TSource>( this IEnumerable<TSource> source );
public static TSource LastOrDefault<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate );
Enumerable.LastOrDefault(TSource) メソッド (IEnumerable(TSource)) (System.Linq)
さあ、使ってみましょう。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // データ int[] numbers = new int[] { 1, 2, 3, 5, 7, 11 }; int result = 0; try { // 20より大きい数値を探すよ! result = numbers.LastOrDefault( value => value > 20 ); } 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}", 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],
結果:0
int
型のデフォルトの値は0ですから、0が返ってきていますね。
リストが空の場合も試しておきましょう。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { static void Main( string[] args ) { // データ(あえて空のデータにしてみました) int[] numbers = new int[] { }; int result = 0; try { // 20より大きい数値を探すよ! result = numbers.LastOrDefault(); } 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}", 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
データ:
結果:0
この場合も例外を出さずに0が返ってきています。
クラスの配列の場合も試しておきましょうか。
Program.cs
using System.Linq; using System.Collections; using System.Collections.Generic; public static class Program { private class Parameter { public int ID { get; set; } public string Name { get; set; } public override string ToString() { return string.Format( "ID:{0}, Name:{1}", ID, Name ); } } static void Main( string[] args ) { // データ Parameter[] parameters = new Parameter[] { new Parameter() { ID = 5, Name = "正一郎" }, new Parameter() { ID = 13, Name = "清次郎" }, new Parameter() { ID = 25, Name = "誠三郎" }, new Parameter() { ID = 42, Name = "征史郎" }, }; Parameter result = new Parameter(); try { // IDが50より大きい要素を取得するよ! result = parameters.LastOrDefault( value => value.ID > 50 ); } catch( System.Exception i_exception ) { System.Console.WriteLine( "例外だよ:{0}", i_exception ); // 入力待ち用 System.Console.ReadKey(); return; } // 結果発表 System.Console.WriteLine( "データ:{0}", parameters.Text() ); 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
データ:[ID:5, Name:正一郎], [ID:13, Name:清次郎], [ID:25, Name:誠三郎], [ID:42, Name:征史郎],
結果:
class
のデフォルト値はnull
のため、結果は空になっています。
LastOrDefault()
の方が便利そうですが、注意したい点があります。
先ほどの例では、要素を探して0が返ってきた場合、「0の要素を見つけた」のか「見つからなかったので、デフォルトの値である0が返ってきた」のかがわからない点です。
その点には気を付けて、七兆回使ってみてください。
LINQのリンク
LINQ一覧
www.urablog.xyzFirst, FirstOrDefault
先頭の要素を取得したい!
www.urablog.xyzSingle, SingleOrDefault
一つだけの要素を取得したい!
www.urablog.xyzElementAt, ElementAtOrDefault
指定したインデックスの要素を取得したい!
www.urablog.xyz