Name capacity and current payload independently
A buffer provides storage capacity, while a read result describes the prefix containing newly obtained bytes. Those quantities coincide only by accident when an operation fills the entire destination. The sample intentionally separates them: the destination has eight slots and begins filled with sentinel bytes, but the source contains only the two bytes for ASCII A and B. This makes unused capacity visible without relying on whatever old values happen to remain in a particular allocation. The consumer's responsibility is to carry the returned count alongside the buffer until the current payload has been processed.
Read the three lines as three different measurements
The nonempty read request offers all eight destination slots, and the memory source copies its two available bytes. The returned count printed on the first line is therefore 2. The second line converts only buffer.AsSpan(0, count) into hexadecimal, yielding 4142. Finally, printing the array's length produces 8. Nothing resized the array to match the payload, and nothing made its trailing six sentinel bytes part of the input. These observations describe logical payload length, payload representation and storage capacity respectively; they are not three alternate ways to measure the same thing.
Carry the valid range through every downstream operation
Slicing a span to the returned count tells the next operation exactly which bytes belong to this read. The same discipline applies when calling an encoding decoder, appending to an accumulator, hashing a segment or writing to another stream. Passing an entire array after a partial fill can introduce stale data, zero bytes or sensitive bytes left by an earlier use. A span is a view over existing storage rather than a separate copy, so it is especially useful for immediate processing. If the data must outlive the buffer's next reuse, an explicit ownership or copying decision is still necessary.
An allocated buffer is not a record container
Many useful algorithms reuse one destination across a loop. On the last iteration, fewer bytes may arrive than on earlier iterations, leaving old values beyond the new prefix. Clearing the tail can hide a visible symptom but does not change the read count or establish framing. Rented arrays introduce the additional fact that their capacity can exceed the requested size and their prior contents are not a message contract. This lesson uses an ordinary owned array so no pool behavior is implied. Correctness should follow from slicing to the logical count, regardless of whether unused storage contains zeros or sentinels.
A nonempty read ending differs from an empty request
After this sample's first read, another request with a nonzero destination length would return zero because the source is exhausted. An empty destination could also return zero while unread data remained, so an algorithm should not infer end from an operation that asked for nothing. Conversely, a positive short read does not establish end for a general stream; it only establishes the prefix available from that operation. A parser that requires a larger fixed record must accumulate further reads. These rules connect this buffer exercise to the preceding exact-read lesson without assuming that every source behaves like MemoryStream.
Expose the tail and then reuse the buffer
For the first exercise, print the hexadecimal representation of the entire eight-byte array beside the correctly sliced representation. Predict the six extra bytes and explain why their presence is a consumer error rather than additional source data. For the second, use the same buffer for a first input containing four bytes and a second input containing one byte, without clearing the array between reads. Print only the new valid prefix each time. Then deliberately print the whole array to identify exactly which bytes came from the previous operation.
- Hint for the original whole-array print: 4142 is followed by six CC byte pairs.
- Hint for reuse: a one-byte read updates only the first slot; the returned count remains the authority for that operation.
Specify the consumer boundary in terms of ranges
A reusable helper can accept a ReadOnlySpan<byte> when it needs only the currently valid range, rather than accepting a large array and guessing a length. An asynchronous owner may instead carry a memory slice or an explicit buffer-and-count pair until completion. In either design, review who may overwrite the storage and when. The sample's conversion is immediate, so the view remains valid for its entire use. Its small output is enough to demonstrate a general rule: capacity describes available storage, whereas a producer's result describes which portion presently has defined meaning for the consumer.
- Preserve the count returned by each read instead of reconstructing it from array length.
- Exclude untouched tail bytes from decoding, hashing and subsequent writes.
- Copy or transfer ownership when data must survive the next buffer reuse.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.IO;
using System.IO.Compression;
using System.Buffers.Binary;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using var input = new MemoryStream(new byte[] { 0x41, 0x42 });
byte[] buffer = Enumerable.Repeat((byte)0xCC, 8).ToArray();
int count = input.Read(buffer, 0, buffer.Length);
Console.WriteLine(count); Console.WriteLine(Convert.ToHexString(buffer.AsSpan(0, count))); Console.WriteLine(buffer.Length);
Expected output
2 4142 8
Carry the logical count alongside storage; the unused tail is not part of the message.