memo:C#の反復と例外
反復周りをメモします。
1.反復します 非Generic
public class StampCollection : System.Collections.IEnumerable { private Dictionary<string, Stamp> stamps_ = new Dictionary<string, Stamp>(); public void Add(Stamp s) { stamps_.Add(s.Name, s); } public System.Collections.IEnumerator GetEnumerator() { //linq var ordertdStamp = from Stamp stamp in stamps_.Values orderby stamp.Year select stamp; foreach (Stamp stamp in ordertdStamp) yield return stamp; } public System.Collections.IEnumerable GetEnumerator2() { //linqでリバース var ordertdStamp = from Stamp stamp in stamps_.Values orderby stamp.Year descending select stamp; foreach (Stamp stamp in (IEnumerable<Stamp>)ordertdStamp) yield return stamp; } } public class Stamp { public int Year { get; set; } public string Name { get; set; } public Stamp(int year, string name) { this.Year = year; this.Name = name; } public override string ToString() { return this.Year + ":" + this.Name; } }
: System.Collections.IEnumerableを実装することで
StampCollection stamps = new StampCollection() { new Stamp(1998,"hoge1"), new Stamp(1999,"hoge2"), new Stamp(2000,"hoge3") }; foreach (Stamp s in stamps) Console.WriteLine(s.ToString()); foreach (Stamp s in stamps.GetEnumerator2()) Console.WriteLine(s.ToString());
{}で初期化時にいろいろ突っ込める。(Addの実装が必要)
IEnumeratorが実装要求するのはSystem.Collections.IEnumerator GetEnumerator()で
独自の反復を定義するときはSystem.Collections.IEnumerable 関数名()という風に
IEnumerableを返す関数なのがチェックポイント。
利用側も
foreach (Stamp s in stamps)
foreach (Stamp s in stamps.関数名())
となる。
2.ゲネリック
いまいち納得できなくて気持ち悪いけど、とりあえず動くし、動く。むむぅ。
public class ShopingList<T> : IEnumerable<T> { private List<T> items_ = new List<T>(); public ShopingList<T> Add(T name) { items_.Add(name); return this; } public IEnumerator<T> GetEnumerator() { return items_.GetEnumerator(); } //独自反復 public IEnumerable<T> GetEnumerator2() { foreach (T item in items_.Reverse<T>()) yield return item; } IEnumerator<T> IEnumerable<T>.GetEnumerator() { return GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } }
Generic版は少し書くこと増える感じ?
利用は同じように可能
ShopingList<string> shopingcart = new ShopingList<string>() { "item1", "item2", "item3" }; //: IEnumerable<T>を実装しているからこんなことが可能↓Listの場合はこんな風 List<string> lis = new List<string>() { "hoge", "hoge" }; for (var i = 0; i < 10; i++) shopingcart.Add("name" + i.ToString()); foreach (string item in shopingcart) Console.WriteLine(item); foreach (string item in shopingcart.GetEnumerator2()) Console.WriteLine(item);
ただし、列挙だけなら
class TestList<T> { List<T> lis_ = new List<T>(); public TestList<T> Add(T item) { lis_.Add(item); return this; } public IEnumerable<T> MyEnumerator() { foreach (T item in lis_) yield return item; } }
IEnumerableを返す関数だけでOK.
3.反復中の例外処理
反復中に例外起きたらどうするのかちょっと気になった
無茶に例外を起きることがあったらどうしましょう?
public IEnumerable<T> GetEnumeratorReigai() { foreach (T item in items_.Reverse<T>()) { if (item.Equals("item2")) { throw new Exception("こんなことは起こらないのでしょうか...?例外発生させました"); } else { //yield return をtry-catchはできない yield return item; } } }
この例外は反復を止めてしまう。
利用側のを↓の用に書いてforeachが終わるのかどうか確かめてみる。
反復定義の中で起きた例外は反復の外側の※1でcatchされる。
yield returnをtry-catchできないのでどうにもならない。そもそもこの例外が起きるようなドン引き必至の緊急事態にforeachが終わらないで良い場合とはどんな時なのか?ということを少し考えて納得した。
foreachは終わってしまう。
try { foreach (string item in shopingcart.GetEnumerator2()) { try { Console.WriteLine(item); throw new Exception("foreach内部で例外発生"); } catch (Exception ex)//※2 { Console.WriteLine("in foreach:" + ex.ToString()); } } } catch (Exception ex) //※1 { Console.WriteLine("out of foreach:" + ex.ToString()); }
一方で利用側で例外が発生した場合(yield returnだとどっちが利用側なんだかよくわかんないネ。。)
throw new Exception("foreach内部で例外発生");はforeachを止めない。
例外発生の都度※2でcatchされる。
独自のイテレータをいっぱい作るときはLINQの美人度におののく。
なんだかもっとかっこいいLINQの使い方でできるらしいけど、それはまた別の機会にしよう。
おしまーい