在实现ReadOnlySequenceSegment <t>时正确使用ReadOnlySequence <t>

时间:2019-05-21 14:50:34

标签: c# .net-core .net-core-2.1

几天以来,我一直在尝试实现网络库以从头开始完成Astron,而我有几个问题。为了正确处理Nagle,我必须具有帧解析逻辑。因此,在使用新的管道API进行此操作时,我注意到输出是作为只读序列提供的,因此我不得不制作一个自定义的SequenceReader,但是在测试它时遇到了一些问题。我认为我误解了新的Span<T>Memory<T> API:/

  1. 模拟序列片段是否有意义?

为了测试尽可能多的行为,我必须实现ReadOnlySequenceSegment<T>抽象类,我使用Moq库来完成该工作,以了解何时获得下一个片段。 :

    public class SequenceSegment : ReadOnlySequenceSegment<byte>
    {
        private readonly Mock<SequenceSegment> _mock;
        private ReadOnlySequenceSegment<byte> _next;

        public new ReadOnlySequenceSegment<byte> Next
        {
            get
            {
                var mockNext = _mock.Object.Next; // simulate get from the mock
                return _next;
            }
            protected set => _next = value;
        }

        public SequenceSegment(ReadOnlyMemory<byte> memory)
        {
            Memory = memory;
            _mock = new Mock<SequenceSegment>(memory);
        }

        public SequenceSegment Add(ReadOnlyMemory<byte> mem)
        {
            var segment = new SequenceSegment(mem)
            {
                RunningIndex = RunningIndex + Memory.Length
            };
            Next = segment;
            return segment;
        }

        public void VerifyGetNext() => _mock.VerifyGet(ss => ss.Next);
    }

如您所见,我不得不用new关键字覆盖Next属性,这是一个不好的做法,但是我认为在测试时还可以吗?

这是未通过的测试:

        [Fact]
        public void TryRead_ShouldReturnTrue_OnSegmentSplitted()
        {
            var buffer = _createBuffer(); // int + int = 8bytes buffer
            var firstSegment = new SequenceSegment(buffer.Slice(0, 3));
            var secondSegment = firstSegment.Add(buffer.Slice(3, 5));

            var input = new ReadOnlySequence<byte>(
                firstSegment, 0,
                secondSegment, 4);

            var reader = new SequenceReader(input);
            reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var firstValue); // throw here
            reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var secondValue);

            Assert.Equal(_firstValue, firstValue);
            Assert.Equal(_secondValue, secondValue);
            firstSegment.VerifyGetNext();
        }

测试输出:

Message: System.ArgumentOutOfRangeException : Specified argument was out of the range of valid values.
Parameter name: start

我注释了在测试中引发异常的行,因此我假设我的序列逻辑还可以吗?让我们看一下我的SequenceReader的代码,并在其中加上注释:

    public class SequenceReader
    {
        private const int _maxStackalloc = 128;
        protected ReadOnlySequence<byte> _input;

        public SequenceReader(ReadOnlySequence<byte> input) => _input = input;

        public delegate T ReadDelegate<out T>(ReadOnlySpan<byte> src);

        /// <summary>
        /// Try to read a <see cref="T"/> with the <see cref="ReadDelegate{T}"/> specified as an arg.
        /// The <see cref="SequenceReader"/> then advance the current position according to the size of <see cref="T"/>.
        /// <see cref="T"/> must be a struct :
        /// <see cref="byte"/>, <see cref="sbyte"/>, <see cref="bool"/>, <see cref="short"/>, 
        /// <see cref="ushort"/>, <see cref="int"/>, <see cref="uint"/>, <see cref="long"/>, 
        /// <see cref="ulong"/>, <see cref="float"/>, <see cref="double"/>, <see cref="decimal"/>, 
        /// </summary>
        /// <typeparam name="T">The type to read.</typeparam>
        /// <param name="read">The delegate to read the <see cref="T"/>. Must be a method from <see cref="BinaryPrimitives"/></param>
        /// <param name="result">The result returned.</param>
        /// <returns>Returns true if the read was successful, else returns false.</returns>
        public unsafe bool TryRead<T>(ReadDelegate<T> read, out T result) where T : unmanaged
        {
            result = default;
            var size = sizeof(T);
            if (size > _maxStackalloc) return false;
            if (size > _input.Length) return false;

            if (_input.First.Length >= size)
                result = read(_input.First.Span);
            else
            {
                Span<byte> local = stackalloc byte[size];
                _input.Slice(size).CopyTo(local); // throws at the slice
                result = read(local);
            }

            _input = _input.Slice(size);
            return true;
        }
    }

我已经尝试过将行更改为_input.Slice(0, size),但是没有任何改变,也没有在此成功的测试中:

        [Fact]
        public void TryRead_ShouldReturnTrue_OnSegmentComplete()
        {
            var input = new ReadOnlySequence<byte>(_createBuffer());

            var reader = new SequenceReader(input);
            reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var firstValue);
            reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var secondValue);

            Assert.Equal(_firstValue, firstValue);
            Assert.Equal(_secondValue, secondValue);
        }

我想知道我在做错什么,如果您有任何想法,请帮助我会很棒!

0 个答案:

没有答案