foreach で break された場合に処理を実行する


表題の話題が挙がったので実験的に。
標準でこういうのない…よね?
アクションの位置をもう少しなんとかしたくはある。

テスト

EnumerableExTest.cs
[TestClass]
public class MyTestClass
{
    [TestMethod]
    public void BreakTest()
    {
        var count = 0;
        foreach (var item in new[] { 1, 2, 3 }.BreakWith(() => count++))
        {
            Console.WriteLine(item);
            if (item == 2) break;
        }

        count.Is(1);
    }
    [TestMethod]
    public void LastBreakTest()
    {
        var count = 0;
        foreach (var item in new[] { 1, 2, 3 }.BreakWith(() => count++))
        {
            Console.WriteLine(item);
            if (item == 3) break;
        }

        count.Is(1);
    }
    [TestMethod]
    public void NotBreakTest()
    {
        var count = 0;
        foreach (var item in new[] { 1, 2, 3 }.BreakWith(() => count++))
        {
            Console.WriteLine(item);
        }

        count.Is(0);
    }
    [TestMethod, ExpectedException(typeof(ArgumentNullException))]
    public void SourceNullTest()
    {
        ((IEnumerable<object>)null).BreakWith(() => { });
    }
    [TestMethod, ExpectedException(typeof(ArgumentNullException))]
    public void ActionNullTest()
    {
        new[] { new object() }.BreakWith(null);
    }
}

実装

EnumerableEx.cs
public static class EnumerableEx
{
    public static IEnumerable<T> BreakWith<T>(this IEnumerable<T> source, Action action)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (action == null) throw new ArgumentNullException("action");

        var enumerated = false;
        try
        {
            foreach (var item in source)
            {
                yield return item;
            }
            enumerated = true;
        }
        finally
        {
            if (!enumerated) action();
        }
    }
}