列挙体とLinq
C#のLInq!便利ですよね^^
気をつけないとなんでもLinqで書きたい中毒者になってしまいそうですww
今回は列挙体をLInqで使う時に少々ハマったのでメモしておきます!
列挙体
「列挙体」と入力したいのに「列挙隊」と変換されるMacに少しイライラww
まぁ…それは置いといて列挙体の解説はこの記事を読んでいる方は大丈夫ですよね^^
// 動物列挙体 public enum Animal { Dog, // 犬 Cat, // 猫 Crow, // カラス Elephant // 象 }
こんな感じで動物の列挙体を定義したとして…
// 動物の鳴き声を取得 public static string GetCryText(Animal animal) { switch(animal) { case Animal.Dog: return "ワン"; case Animal.Cat: return "ニャー"; case Animal.Crow: return "カァー"; case Animal.Elephant: return "パオーン"; default: // 警告出るので一応書いておく return "..."; } }
このように鳴き声を取得する関数などを作った時に可読性が良くなります。
※switch式使えば?という話はとりあえずおいといて^^
Enumで定義されている値を全取得
では、4種類全ての動物を取得したい場合はどう書けば良いのでしょうか?
列挙体で定義されている値を全て取得したいということですね。
Array Enum.GetAllValues(Type type);
上記メソッドを使えば列挙体で定義されている値を全て取得できます。type
に列挙体の型を渡すことで列挙体に定義されている全ての値をArray
型(配列)で取得できます。
つまり…
// Animalを全て取得 foreach(var animal in Enum.GetValues(typeof(Animal))) { Console.WriteLine(animal.ToString()); } /**** 出力: Dog Cat Crow Elephant ****/
このようにすればAnimal
列挙体の全ての値を取得できるわけですね!
全ての動物の鳴き声を取得する
前節で、列挙体に定義されている全ての値を取得することができたので後は簡単!
foreach(var i in Enum.GetValues(typeof(Animal))) { Console.WriteLine(GetCryText(i)); } // (注意)コンパイルエラー
上のようにすれば全ての鳴き声が取得できる~…とはいきませんww
気づいている方も多いはず…Enum.GetValues
メソッドで返されるオブジェクトはArray
型なんです。実はこのArray
型、非ジェネリック型です。実はi
の型はobject
型なんです。
public static string GetCryText(Animal animal);
と定義したのに、引数にobject
型を渡していればエラーが出ますよね?
さっき成功したのはi.ToString()
としていたから…たまたまobject
のメソッドを使っていたから上手くいっていただけなんです。
foreach(var i in Enum.GetValues(typeof(Animal))) { Console.WriteLine(GetCryText((Animal)i)); }
つまり上記のようにAnimal
型にキャストしてあげないと上手くコンパイルが通りません。
LInqを使いたい!
前置きが長くなりましたがここからが本題です。
今まで列挙体で定義されている値を全て取り出してコンソールに出力する…ということをやってきた訳ですが、見出しの通り「Linqが使いたい」場合にどうするか考えてみます。
Linqを使うメリットは?
「なぜLInqを使うの?」という疑問が浮かぶ人もいるかもしれません。もちろん前述したような列挙体の値を全て出力するだけなら前述の方法でも構いません。
例えば「Animal
型に定義されている動物の、鳴き声が3文字のものだけをList<string>
として保存したい」のような場合はどうでしょうか?
var list = new List<string>(); foreach(var i in Enum.GetValues(typeof(Animal))) { var cry = GetCryText((Animal)i); if(cry.Length == 3) { list.Add(cry); } }
今までのやり方だと上記のようなコードになるでしょう。
でもLinqを使って…
var list = Enum.GetValues(typeof(Animal)) .Select(v => GetCryText(v)) // Selectが定義されていない .Where(v => v.Length == 3) .ToList(); // (注意)コンパイルエラー
上記のようにかけたら気持ち良くないですか?とてもすっきりと書けますよね?もちろん上記はエラーとなりますが、このように書きたいと思ったんです!
Array
型はIEnumerable<T>
を実装していない
前述の通りArray
型は非ジェネリック型…つまりIEnumerable
インターフェースは実装していますが、IEnumerable<T>
インターフェースは実装していないんです。
しかし、LinqのほとんどはIEnumerable<T>
の拡張メソッド群であり、IEnumerable
には使えないメソッドが多いんです。Select
メソッドもその一つです。
Cast<T>()
メソッドって使えないの?
私がまず一番に考えたのがCast<T>()
メソッド…
var list = Enum.GetValues(typeof(Animal)) .Cast<Animal>() // Castが定義されていない .Select(v => GetCryText(v)) .Where(v => v.Length == 3) .ToList(); // (注意)コンパイルエラー
Cast<Animal>()
と書けばIEnumerable<Animal>
に変換してくれるだろう…とか考えたのですが、実はCast<T>()
メソッドもIEnumerable<T>
型の拡張メソッドなんです…(T_T)
なので、上記のようなコードを書いてもCast
メソッドが見つからない的なエラーでコンパイルエラーとなります。
「ではどうすればいいんだ?」自作拡張メソッドなど、いろいろ手を考えましたがスマートな方法が見当たらない…と思ったその時!「あった~!ドンピシャ~」というメソッドを見つけました^^
結果:OfType<T>()
メソッドが正解?
OfType<T>()
メソッド!見たときはCast<T>()
と何が違うの?…とか思いましたww
Cast
メソッドはIEnumerable<T1>
の要素全てをキャストしてIEnumerable<T2>
のように変換してしまうメソッドです。T1からT2にキャスト出来ない場合は例外を出します。
対してOfType<T>()
メソッドはIEnumerable
の全ての要素から型がTのものだけを取り出すメソッドです。なので、キャスト出来ない要素に関しては切り捨てられます。「型がTのものだけを取り出す」ので当然戻り値はIEnumerable<T>
です。つまり、結果的にはIEnumerable
からIEnumerable<T>
に変換を行うことができるメソッドなんです!
var list = Enum.GetValues(typeof(Animal)) .OfType<Animal>() // これが重要! .Select(v => GetCryText(v)) .Where(v => v.Length == 3) .ToList(); /**** listの中身: "ニャー" "カァー" ****/
出来た~\(^0^)/
「Animal
型に定義されている動物の、鳴き声が3文字のものだけをList<string>
として保存したい」をLInqを使って実装できました!
まとめ
最後まで読んでいただきありがとうございました!
他にも良い方法などあればお気軽にコメントお願いします^^
OfType
メソッドは他にも使い道がありそうなので覚えていて損はないですね!
list.Where(v => v is (TestType));
という書き方をするなら
list.OfType<TestType>();
と書き直した方がスマートかもしれませんね!
OfType
メソッドのリファレンスが見たい方はこちら!