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の使い方でできるらしいけど、それはまた別の機会にしよう。


おしまーい