雑記C#

EnumをLInqで使いたい!

列挙体と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メソッドのリファレンスが見たい方はこちら

「麻雀」+「IT」の事はご相談を!
麻雀とIT、パソコンの事の相談窓口

相談だけでも構いません^^
何か私に出来る仕事は無いですか?
話して初めて見つかるアイデアもあります!
お気軽に相談してくれれば幸いです

  • 大会卓割作成
  • 大会集計システム
  • ポスター・ポップ作成
  • 動画編集
  • 文書・印刷物作成
  • ホームページ・ブログ作成
  • Windowsソフト開発
  • iOS・Androidアプリ開発
日本プロ麻雀連盟第38期(四国支部 第1期)
平川 一樹
名前:平川 一樹
生年月日:1991年11月24日
職業:プログラマー・Webプログラマー・デザイナー
所属:日本プロ麻雀連盟 四国支部 第1期生
主にIT分野を生かして、麻雀業界に貢献する方法をいつも考えています。
よろしければTwitterなどフォローお願いします^^
\ Follow me /

良かったらコメントしてね!