【Practical C#】C#中的迭代器 Iterator(IEnumerable和IEnumerator)

网上关于C#的迭代器教程也很多,但很多例程只是简单的封装一个可以迭代的实例变量,然后直接返回此实例变量的迭代器或者使用yield return语句。这样的例程没有触及到真正迭代器的实现。


问题域描述:

给定一个整数序列,统计连续的奇数或者偶数的个数。

例如对于以下整数序列

1, 2, 4, 10, 10, 8, 25, 13, 5, 7

输出:

IsOdd = True, Count = 1 (根据 1 中统计得到)
IsOdd = False, Count = 5 (根据 2, 4, 10, 10, 8 统计得到)

IsOdd = True, Count = 4 (根据 25, 13, 5, 7 统计得到)

这样的问题应该比较简单,大家都比较容易理解问题域本身。

在此例程中,我试着用TDD的方式去解决问题,所以以下单元测试很有参考价值。虽然需要写更多的代码,但TDD的方式确实还是值得推崇的。


Solution


该示例是在VS2017 + NUnit中演示的。

AnalysisItemEnumerator(迭代器)实现IEnumerator<AnalysisItem>

Analyzer(可迭代)实现IEnumerable<AnalysisItem>

再次强调那些单元测试真的能驱动开发程序。

AnalysisItem.cs

    public class AnalysisItem : IEquatable<AnalysisItem>
    {
        public bool IsOdd { get; }

        public int Count { get; }

        public AnalysisItem(bool isOdd, int count)
        {
            IsOdd = isOdd;
            Count = count;
        }

        public override string ToString()
        {
            return $"IsOdd = {IsOdd}, Count = {Count}";
        }

        public bool Equals(AnalysisItem other)
        {
            if (ReferenceEquals(null, other))
                return false;
            if (ReferenceEquals(this, other))
                return true;
            if (GetType() != other.GetType())
                return false;

            return IsOdd == other.IsOdd && Count == other.Count;
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as AnalysisItem);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return (IsOdd.GetHashCode() * 397) ^ Count;
            }
        }

        public static bool operator ==(AnalysisItem left, AnalysisItem right)
        {
            return ReferenceEquals(left, null)
                ? ReferenceEquals(right, null)
                : left.Equals(right);
        }

        public static bool operator !=(AnalysisItem left, AnalysisItem right)
        {
            return !(left == right);
        }
    }

说明

  • ToString(): 我们应该实现ToString,VS或者其他工具很多地方都会调用ToString来显示当前实例的信息。不然默认的实现都是一样的,无法区分
  • IEquatable: 我们实现IEquatable是为了单元测试的需要

AnalysisItemTests.cs

    [TestFixture]
    public class AnalysisItemTests
    {
        [Test]
        public void Equal_TwoEqualItems_ShouldBeEqual()
        {
            var item1 = new AnalysisItem(true, 1);
            var item2 = new AnalysisItem(true, 1);

            Assert.That(item1.Equals(item2), Is.True);
            Assert.That(item2.Equals(item1), Is.True);

            Assert.That(item1 == item2, Is.True);

            Assert.That(item1 != item2, Is.False);
        }

        [Test]
        public void Equal_OneItemVsNull_ShouldNotBeEqual()
        {
            var item = new AnalysisItem(true, 1);

            Assert.That(item.Equals(null), Is.False);

            Assert.That(item == null, Is.False);
            Assert.That(null == item, Is.False);

            Assert.That(item != null, Is.True);
            Assert.That(null != item, Is.True);
        }

        [Test]
        public void Equal_TwoItemsWithDifferentIsOdd_ShouldNotBeEqual()
        {
            var item1 = new AnalysisItem(true, 1);
            var item2 = new AnalysisItem(false, 1);

            Assert.That(item1.Equals(item2), Is.False);
            Assert.That(item2.Equals(item1), Is.False);

            Assert.That(item1 == item2, Is.False);
            Assert.That(item2 == item1, Is.False);

            Assert.That(item1 != item2, Is.True);
            Assert.That(item2 != item1, Is.True);
        }

        [Test]
        public void Equal_TwoItemsWithDifferentCount_ShouldNotBeEqual()
        {
            var item1 = new AnalysisItem(true, 1);
            var item2 = new AnalysisItem(true, 2);

            Assert.That(item1.Equals(item2), Is.False);
            Assert.That(item2.Equals(item1), Is.False);

            Assert.That(item1 == item2, Is.False);
            Assert.That(item2 == item1, Is.False);

            Assert.That(item1 != item2, Is.True);
            Assert.That(item2 != item1, Is.True);
        }
    }

说明:

有些人可能认为一个单元测试中应该只有一个Assert,但个人觉得那太教条了。只要Assert是同一个“单元”,多个Assert也是可以的。


AnalysisItemEnumerator.cs

    public class AnalysisItemEnumerator : IEnumerator<AnalysisItem>
    {
        private readonly IReadOnlyList<int> _numbers;
        private int _currentIndex = -1;
        private bool _currentProcessed = true;
        private AnalysisItem _currentAnalysisItem;

        public AnalysisItemEnumerator(IReadOnlyList<int> numbers)
        {
            _numbers = numbers ?? throw new ArgumentNullException(nameof(numbers));
        }

        public void Dispose() { }

        public bool MoveNext()
        {
            var haveNext = _currentIndex + 1 < _numbers.Count;

            if (haveNext)
                _currentProcessed = false;
            else
            {
                _currentProcessed = true;
                _currentAnalysisItem = null;
            }

            return haveNext;
        }

        public void Reset()
        {
            _currentIndex = -1;
            _currentProcessed = true;
            _currentAnalysisItem = null;
        }

        public AnalysisItem Current
        {
            get
            {
                if (!_currentProcessed)
                {
                    ++_currentIndex;
                    var isOdd = IsOdd(_numbers[_currentIndex]);
                    var count = 1;

                    while (++_currentIndex < _numbers.Count)
                    {
                        if (IsOdd(_numbers[_currentIndex]) == isOdd)
                            ++count;
                        else
                        {
                            --_currentIndex;
                            break;
                        }
                    }

                    _currentAnalysisItem = new AnalysisItem(isOdd, count);
                    _currentProcessed = true;
                }
                return _currentAnalysisItem;
            }
        }

        private static bool IsOdd(int number)
        {
            return number % 2 == 1;
        }

        object IEnumerator.Current
        {
            get { return Current; }
        }
    }

AnalysisItemEnumeratorTests.cs

    [TestFixture]
    public class AnalysisItemEnumeratorTests
    {
        private readonly IReadOnlyList<int> _numbers = new[] { 1, 3, 5, 2, 7, 9 };
        private AnalysisItemEnumerator _enumerator;

        [SetUp]
        public void SetUp()
        {
            _enumerator = new AnalysisItemEnumerator(_numbers);
        }

        [Test]
        public void Constructor_WhileNumbersIsNull_ShouldThrowArgumentNullException()
        {
            Assert.Throws<ArgumentNullException>(() => new AnalysisItemEnumerator(null));
        }

        [Test]
        public void Current_WhileCalledAtFirstTime_ShouldReturnNull()
        {
            Assert.That(_enumerator.Current, Is.Null);
        }

        [Test]
        public void EnumeratingItem_AfterEachEnumeration_ShouldBehaveAsExpected()
        {
            Assert.That(_enumerator.MoveNext, Is.True);
            Assert.That(_enumerator.Current, Is.EqualTo(new AnalysisItem(true, 3)));

            Assert.That(_enumerator.MoveNext, Is.True);
            Assert.That(_enumerator.Current, Is.EqualTo(new AnalysisItem(false, 1)));

            Assert.That(_enumerator.MoveNext, Is.True);
            Assert.That(_enumerator.Current, Is.EqualTo(new AnalysisItem(true, 2)));

            Assert.That(_enumerator.MoveNext, Is.False);
            Assert.That(_enumerator.Current, Is.Null);
        }

        [Test]
        public void Current_WhileCalledTwice_ShouldReturnSameItem()
        {
            _enumerator.MoveNext();

            var current1 = _enumerator.Current;
            var current2 = _enumerator.Current;

            Assert.That(current1, Is.SameAs(current2));
        }

        [Test]
        public void Reset_WhileEnumeratingItemsAgain_FirstCurrentShouldBeNull()
        {
            _enumerator.MoveNext();

            _enumerator.Reset();

            Assert.That(_enumerator.Current, Is.Null);
        }

        [Test]
        public void Reset_WhileEnumeratingItemsAgain_ShouldEnumerateFromFirstItem()
        {
            _enumerator.MoveNext();
           
            _enumerator.Reset();

            Assert.That(_enumerator.MoveNext, Is.True);
            Assert.That(_enumerator.Current, Is.EqualTo(new AnalysisItem(true, 3)));
        }
    }

Analyzer.cs

    public class Analyzer : IEnumerable<AnalysisItem>
    {
        private readonly IReadOnlyList<int> _numbers;

        public Analyzer(IReadOnlyList<int> numbers)
        {
            _numbers = numbers ?? new int[] { };
        }

        public IEnumerator<AnalysisItem> GetEnumerator()
        {
            return new AnalysisItemEnumerator(_numbers);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

Program.cs

    class Program
    {
        static void Main(string[] args)
        {
            var numbers = new[] { 1, 2, 4, 10, 10, 8, 25, 13, 5, 7 };
            var analyzer = new Analyzer(numbers);

            foreach (var item in analyzer)
                Console.WriteLine(item);

            Console.ReadKey();
        }
    }






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值