Formazione
Modulo
Informazioni su come creare variabili di matrice ed eseguire l'iterazione degli elementi della matrice.
Questo browser non è più supportato.
Esegui l'aggiornamento a Microsoft Edge per sfruttare i vantaggi di funzionalità più recenti, aggiornamenti della sicurezza e supporto tecnico.
Questo articolo offre una panoramica dei tipi che consentono di leggere i dati eseguiti in più buffer. Vengono usati principalmente per supportare gli oggetti PipeReader.
System.Buffers.IBufferWriter<T> è un contratto per la scrittura memorizzata nel buffer sincrono. Al livello più basso, l'interfaccia:
Memory<T>
o Span<T>
possono essere scritti ed è possibile determinare il numero di elementi T
scritti.void WriteHello(IBufferWriter<byte> writer)
{
// Request at least 5 bytes.
Span<byte> span = writer.GetSpan(5);
ReadOnlySpan<char> helloSpan = "Hello".AsSpan();
int written = Encoding.ASCII.GetBytes(helloSpan, span);
// Tell the writer how many bytes were written.
writer.Advance(written);
}
Metodo precedente:
IBufferWriter<byte>
utilizzando GetSpan(5)
.Span<byte>
restituito.Questo metodo di scrittura usa il buffer Memory<T>
/Span<T>
fornito da IBufferWriter<T>
. In alternativa, il metodo di estensione Write può essere usato per copiare un buffer esistente nel IBufferWriter<T>
. Write
esegue il lavoro di chiamata a GetSpan
/Advance
in base alle esigenze, quindi non è necessario chiamare Advance
dopo la scrittura:
void WriteHello(IBufferWriter<byte> writer)
{
byte[] helloBytes = Encoding.ASCII.GetBytes("Hello");
// Write helloBytes to the writer. There's no need to call Advance here
// since Write calls Advance.
writer.Write(helloBytes);
}
ArrayBufferWriter<T> è un'implementazione di IBufferWriter<T>
il cui archivio di backup è una singola matrice contigua.
GetSpan
e GetMemory
restituiscono un buffer con almeno la quantità di memoria richiesta. Non presupporre dimensioni esatte del buffer.Advance
per continuare a scrivere altri dati. Non è possibile scrivere un buffer acquisito in precedenza dopo la chiamata a Advance
.ReadOnlySequence<T> è uno struct che può rappresentare una sequenza contigua o non contigua di T
. Può essere costruito da:
T[]
ReadOnlyMemory<T>
La terza rappresentazione è quella più interessante perché ha implicazioni sulle prestazioni su varie operazioni su ReadOnlySequence<T>
:
Rappresentazione | Operazione | Complessità |
---|---|---|
T[] /ReadOnlyMemory<T> |
Length |
O(1) |
T[] /ReadOnlyMemory<T> |
GetPosition(long) |
O(1) |
T[] /ReadOnlyMemory<T> |
Slice(int, int) |
O(1) |
T[] /ReadOnlyMemory<T> |
Slice(SequencePosition, SequencePosition) |
O(1) |
ReadOnlySequenceSegment<T> |
Length |
O(1) |
ReadOnlySequenceSegment<T> |
GetPosition(long) |
O(number of segments) |
ReadOnlySequenceSegment<T> |
Slice(int, int) |
O(number of segments) |
ReadOnlySequenceSegment<T> |
Slice(SequencePosition, SequencePosition) |
O(1) |
A causa di questa rappresentazione mista, ReadOnlySequence<T>
espone gli indici come SequencePosition
anziché un numero intero. Un oggetto SequencePosition
:
ReadOnlySequence<T>
in cui ha avuto origine.ReadOnlySequence<T>
.ReadOnlySequence<T>
espone i dati come enumerabili di ReadOnlyMemory<T>
. L'enumerazione di ogni segmento può essere eseguita usando un foreach di base:
long FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)
{
long position = 0;
foreach (ReadOnlyMemory<byte> segment in buffer)
{
ReadOnlySpan<byte> span = segment.Span;
var index = span.IndexOf(data);
if (index != -1)
{
return position + index;
}
position += span.Length;
}
return -1;
}
Il metodo precedente cerca in ogni segmento un byte specifico. Se è necessario tenere traccia di ogni SequencePosition
del segmento, ReadOnlySequence<T>.TryGet è più appropriato. L'esempio successivo modifica il codice precedente in modo da restituire un valore SequencePosition
invece di un numero intero. La restituzione di un oggetto SequencePosition
ha il vantaggio di consentire al chiamante di evitare una seconda analisi per ottenere i dati in un indice specifico.
SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)
{
SequencePosition position = buffer.Start;
SequencePosition result = position;
while (buffer.TryGet(ref position, out ReadOnlyMemory<byte> segment))
{
ReadOnlySpan<byte> span = segment.Span;
var index = span.IndexOf(data);
if (index != -1)
{
return buffer.GetPosition(index, result);
}
result = position;
}
return null;
}
La combinazione di SequencePosition
e TryGet
agisce come un enumeratore. Il campo di posizione viene modificato all'inizio di ogni iterazione in modo da iniziare ogni segmento all'interno di ReadOnlySequence<T>
.
Il metodo precedente esiste come metodo di estensione in ReadOnlySequence<T>
. PositionOf può essere usato per semplificare il codice precedente:
SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data) => buffer.PositionOf(data);
L'elaborazione di ReadOnlySequence<T>
può risultare complessa perché i dati possono essere suddivisi tra più segmenti all'interno della sequenza. Per ottenere prestazioni ottimali, suddividere il codice in due percorsi:
Esistono alcuni approcci che possono essere usati per elaborare i dati in sequenze con più segmenti:
SequenceReader<T>
.SequencePosition
e dell'indice all'interno del segmento analizzato. In questo modo si evitano allocazioni non necessarie, ma possono risultare inefficienti, soprattutto per i buffer di piccole dimensioni.ReadOnlySequence<T>
in una matrice contigua e considerarla come un singolo buffer: ReadOnlySequence<T>
è piccola, può essere ragionevole copiare i dati in un buffer allocato dallo stack usando l'operatore stackalloc.ReadOnlySequence<T>
in una matrice in pool usando ArrayPool<T>.Shared.ReadOnlySequence<T>.ToArray()
. Questo non è consigliato nei percorsi ad accesso frequente perché alloca un nuovo T[]
nell'heap.Gli esempi seguenti illustrano alcuni casi comuni per l'elaborazione ReadOnlySequence<byte>
:
Nell'esempio seguente viene analizzata una lunghezza intera big-endian a 4 byte dall'inizio di ReadOnlySequence<byte>
.
bool TryParseHeaderLength(ref ReadOnlySequence<byte> buffer, out int length)
{
// If there's not enough space, the length can't be obtained.
if (buffer.Length < 4)
{
length = 0;
return false;
}
// Grab the first 4 bytes of the buffer.
var lengthSlice = buffer.Slice(buffer.Start, 4);
if (lengthSlice.IsSingleSegment)
{
// Fast path since it's a single segment.
length = BinaryPrimitives.ReadInt32BigEndian(lengthSlice.First.Span);
}
else
{
// There are 4 bytes split across multiple segments. Since it's so small, it
// can be copied to a stack allocated buffer. This avoids a heap allocation.
Span<byte> stackBuffer = stackalloc byte[4];
lengthSlice.CopyTo(stackBuffer);
length = BinaryPrimitives.ReadInt32BigEndian(stackBuffer);
}
// Move the buffer 4 bytes ahead.
buffer = buffer.Slice(lengthSlice.End);
return true;
}
L'esempio seguente:
\r\n
) in ReadOnlySequence<byte>
e la restituisce tramite il parametro out 'line'.\r\n
dal buffer di input.static bool TryParseLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> line)
{
SequencePosition position = buffer.Start;
SequencePosition previous = position;
var index = -1;
line = default;
while (buffer.TryGet(ref position, out ReadOnlyMemory<byte> segment))
{
ReadOnlySpan<byte> span = segment.Span;
// Look for \r in the current segment.
index = span.IndexOf((byte)'\r');
if (index != -1)
{
// Check next segment for \n.
if (index + 1 >= span.Length)
{
var next = position;
if (!buffer.TryGet(ref next, out ReadOnlyMemory<byte> nextSegment))
{
// You're at the end of the sequence.
return false;
}
else if (nextSegment.Span[0] == (byte)'\n')
{
// A match was found.
break;
}
}
// Check the current segment of \n.
else if (span[index + 1] == (byte)'\n')
{
// It was found.
break;
}
}
previous = position;
}
if (index != -1)
{
// Get the position just before the \r\n.
var delimeter = buffer.GetPosition(index, previous);
// Slice the line (excluding \r\n).
line = buffer.Slice(buffer.Start, delimeter);
// Slice the buffer to get the remaining data after the line.
buffer = buffer.Slice(buffer.GetPosition(2, delimeter));
return true;
}
return false;
}
È valido archiviare segmenti vuoti all'interno di un oggetto ReadOnlySequence<T>
. I segmenti vuoti possono verificarsi durante l'enumerazione esplicita dei segmenti:
static void EmptySegments()
{
// This logic creates a ReadOnlySequence<byte> with 4 segments,
// two of which are empty.
var first = new BufferSegment(new byte[0]);
var last = first.Append(new byte[] { 97 })
.Append(new byte[0]).Append(new byte[] { 98 });
// Construct the ReadOnlySequence<byte> from the linked list segments.
var data = new ReadOnlySequence<byte>(first, 0, last, 1);
// Slice using numbers.
var sequence1 = data.Slice(0, 2);
// Slice using SequencePosition pointing at the empty segment.
var sequence2 = data.Slice(data.Start, 2);
Console.WriteLine($"sequence1.Length={sequence1.Length}"); // sequence1.Length=2
Console.WriteLine($"sequence2.Length={sequence2.Length}"); // sequence2.Length=2
// sequence1.FirstSpan.Length=1
Console.WriteLine($"sequence1.FirstSpan.Length={sequence1.FirstSpan.Length}");
// Slicing using SequencePosition will Slice the ReadOnlySequence<byte> directly
// on the empty segment!
// sequence2.FirstSpan.Length=0
Console.WriteLine($"sequence2.FirstSpan.Length={sequence2.FirstSpan.Length}");
// The following code prints 0, 1, 0, 1.
SequencePosition position = data.Start;
while (data.TryGet(ref position, out ReadOnlyMemory<byte> memory))
{
Console.WriteLine(memory.Length);
}
}
class BufferSegment : ReadOnlySequenceSegment<byte>
{
public BufferSegment(Memory<byte> memory)
{
Memory = memory;
}
public BufferSegment Append(Memory<byte> memory)
{
var segment = new BufferSegment(memory)
{
RunningIndex = RunningIndex + Memory.Length
};
Next = segment;
return segment;
}
}
Il codice precedente crea un oggetto ReadOnlySequence<byte>
con segmenti vuoti e mostra come questi segmenti vuoti influiscono sulle varie API:
ReadOnlySequence<T>.Slice
con un SequencePosition
che punta a un segmento vuoto mantiene tale segmento.ReadOnlySequence<T>.Slice
con un int ignora i segmenti vuoti.ReadOnlySequence<T>
enumera i segmenti vuoti.Esistono diversi risultati insoliti quando si tratta di un confronto tra un oggetto ReadOnlySequence<T>
/SequencePosition
e un normale: ReadOnlySpan<T>
/ReadOnlyMemory<T>
/T[]
/int
SequencePosition
è un marcatore di posizione per un oggetto specifico ReadOnlySequence<T>
, non una posizione assoluta. Poiché è relativo a un oggetto ReadOnlySequence<T>
specifico, non ha significato se usato all'esterno di ReadOnlySequence<T>
in cui ha avuto origine.SequencePosition
senza ReadOnlySequence<T>
. Ciò significa che le operazioni di base come position++
sono scritte position = ReadOnlySequence<T>.GetPosition(1, position)
.GetPosition(long)
non supporta indici negativi. Ciò significa che è impossibile raggiungere il penultimo carattere senza percorrere tutti i segmenti.SequencePosition
, il che rende difficile: ReadOnlySequence<T>
è maggiore di un riferimento a un oggetto e deve essere passato da in o ref, ove possibile. Il passaggio di ReadOnlySequence<T>
da in
o ref
riduce le copie dello struct.ReadOnlySequence<T>
.ReadOnlySequence<T>.TryGet
.ReadOnlySequence<T>.Slice()
con oggetti SequencePosition
.ReadOnlySequence<T>
.ReadOnlySequence<T>
a segmento singolo e ReadOnlySequence<T>
multi segmento.byte
e char
) che possono essere suddivisi tra segmenti o meno.Esistono metodi predefiniti per gestire l'elaborazione di dati binari e delimitati. La sezione seguente illustra l'aspetto di questi stessi metodi con SequenceReader<T>
:
SequenceReader<T>
dispone di metodi per enumerare i dati direttamente all'interno di ReadOnlySequence<T>
. Il codice seguente è un esempio di elaborazione di un oggetto ReadOnlySequence<byte>
byte
alla volta:
while (reader.TryRead(out byte b))
{
Process(b);
}
CurrentSpan
espone l’oggetto Span
del segmento corrente, che è simile a quello eseguito manualmente nel metodo.
Il codice seguente è un'implementazione di esempio di FindIndexOf
che usa SequenceReader<T>
:
SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)
{
var reader = new SequenceReader<byte>(buffer);
while (!reader.End)
{
// Search for the byte in the current span.
var index = reader.CurrentSpan.IndexOf(data);
if (index != -1)
{
// It was found, so advance to the position.
reader.Advance(index);
return reader.Position;
}
// Skip the current segment since there's nothing in it.
reader.Advance(reader.CurrentSpan.Length);
}
return null;
}
Nell'esempio seguente viene analizzata una lunghezza intera big-endian a 4 byte dall'inizio di ReadOnlySequence<byte>
.
bool TryParseHeaderLength(ref ReadOnlySequence<byte> buffer, out int length)
{
var reader = new SequenceReader<byte>(buffer);
return reader.TryReadBigEndian(out length);
}
static ReadOnlySpan<byte> NewLine => new byte[] { (byte)'\r', (byte)'\n' };
static bool TryParseLine(ref ReadOnlySequence<byte> buffer,
out ReadOnlySequence<byte> line)
{
var reader = new SequenceReader<byte>(buffer);
if (reader.TryReadTo(out line, NewLine))
{
buffer = buffer.Slice(reader.Position);
return true;
}
line = default;
return false;
}
SequenceReader<T>
è uno struct modificabile, deve essere sempre passato per riferimento.SequenceReader<T>
è uno struct di riferimento in modo che possa essere usato solo nei metodi sincroni e non può essere archiviato nei campi. Per altre informazioni, vedere Evitare allocazioni.SequenceReader<T>
è ottimizzato per l'uso come lettore forward-only. Rewind
è destinato a backup di piccole dimensioni che non possono essere risolti usando altre API Read
, Peek
e IsNext
.Feedback su .NET
.NET è un progetto di open source. Selezionare un collegamento per fornire feedback:
Formazione
Modulo
Informazioni su come creare variabili di matrice ed eseguire l'iterazione degli elementi della matrice.