C#
- Apresentando Memory<T>
![]() |
Hoje vou apresentar a struct Memory<T> seus principais métodos e propriedades. |
O recurso Memory<T> é uma struct introduzida no C# 7.2 que representa uma região de memória acessível e mutável que oferece uma abstração para trabalhar com dados na memória de forma eficiente, especialmente em cenários de leitura e gravação de grandes quantidades de dados. (O Memory<T> é semelhante ao Span<T>, mas com algumas diferenças importantes.)
Desta forma , Memory<T> é um tipo de referência que representa uma região contígua de memória e tem um comprimento, mas não necessariamente começa no índice 0 e pode ser uma das muitas regiões dentro de outra Memory. A memória representada por Memory pode nem ser do seu próprio processo, pois pode ter sido alocada em código não gerenciado.
Podemos destacar as seguintes características e usos da struct Memory<T> :
Memory<T>
permite acessar e manipular dados em uma
região de memória. Ele fornece métodos para leitura e gravação eficientes,
sem a necessidade de copiar os dados para outro local.Memory<T>
é seguro em termos de tipo e permite trabalhar
com uma ampla variedade de fontes de dados, como arrays, segmentos de
arrays, memória não gerenciada e até mesmo pools de memória. Ele oferece uma
maneira flexível de trabalhar com dados, independentemente de sua origem.Memory<T>
é útil quando você deseja reutilizar a memória de uma
operação anterior. Em vez de alocar novos arrays ou buffers a cada vez, você
pode criar um Memory<T>
que abrange a região de memória
existente e reutilizá-lo várias vezes.A struct
Memory<T>
possui vários métodos úteis que facilitam a
manipulação e o processamento de dados na memória. Aqui estão alguns dos
principais métodos:
1 -
Memory<T>
que representa uma subseção contígua do Memory<T>
original.
2 -
Span: Obtém um Span<T>
que representa o mesmo
intervalo de memória do Memory<T>
.
3 -
ToArray: Converte o Memory<T>
em um array de
tipo T
4 -
CopyTo: Copia os elementos do Memory<T>
para um
Span<T>
de destino
5 -
Pin: Fixa o objeto Memory<T>
na memória,
retornando um MemoryHandle
que permite o acesso direto à memória
não gerenciada subjacente
Existem também outros métodos, como SequenceEqual
,
GetEnumerator
, GetHashCode
, entre outros, que
fornecem funcionalidades adicionais para trabalhar com dados na memória de forma
eficiente e flexível.
A seguir temos algumas propriedades da struct
Memory<T>q
ue fornecem informações
sobre a região de memória representada :
Length
: Obtém o número de elementos no
Memory<T>
.IsEmpty
: Indica se o Memory<T>
está vazio, ou seja, se o comprimento é igual a zero. IsEmpty
(em C# 10): Indica se o
Memory<T>
está vazio, ou seja, se o comprimento é igual a Span
: Obtém um Span<T>
que
representa o mesmo intervalo de memória do Memory<T>
. Tanto Memory<T> como Span<T> são wrappers sobre buffers de dados estruturados que podem ser usados em pipelines. Assim você deve considerar que os buffers podem ser transmitidos entre APIs e, às vezes, podem ser acessados de várias threads, portanto, esteja ciente de como o tempo de vida de um buffer esta sendo gerenciado.
Agora, embora Span<T> e Memory<T> representem um bloco contíguo de memória, ao contrário de Span<T>, Memory<T> não é uma ref struct.
Portanto, ao contrário do Span<T>, você pode ter Memory<T> em qualquer lugar no heap gerenciado,e assim, você não tem as mesmas restrições em Memory<T> como em Span<T> e pode usar Memory<T> como um campo de classe e além dos limites de await e yield.
Exemplo de uso de Memory<T>
A seguir temos um exemplo básico e simples em um cenário onde você precisa processar um grande arquivo de dados.
Para isso vamos criar um projeto Console App chamado CSharpMemory no ambiente do .NET 7.0 com o seguinte código:
namespace
CShapMemory; public class Program{ static void Main(string[] args) { string filePath = "caminho/para/arquivo.bin"; using (FileStream fileStream = File.OpenRead(filePath)) { // Alocar memória para armazenar o conteúdo do arquivo byte[] buffer = new byte[fileStream.Length]; // Ler o conteúdo do arquivo para o buffer fileStream.Read(buffer, 0, buffer.Length); // Criar um Memory<byte> para representar o conteúdo do arquivo Memory<byte> memory = new Memory<byte>(buffer); // Processar os dados ProcessarDados(memory); } } static void ProcessarDados(Memory<byte> dataMemory) { // Acessar os dados do Memory<byte> como um Span<byte> Span<byte> dataSpan = dataMemory.Span; // Realizar operações de processamento nos dados for (int i = 0; i < dataSpan.Length; i++) { // Processar cada byte individualmente byte resultado = ProcessarByte(dataSpan[i]); // Atualizar o byte com o resultado do processamento dataSpan[i] = resultado; } // Exibir os dados processados ExibirDados(dataSpan); } static byte ProcessarByte(byte dataByte) { // Aplicar alguma lógica de processamento ao byte e retornar o resultado // Exemplo: Inverter o valor do byte return (byte)~dataByte; } static void ExibirDados(Span<byte> dataSpan) { // Exibir os dados processados foreach (byte value in dataSpan) { Console.WriteLine(value); } } } |
No código acima, estamos lendo o conteúdo de um arquivo binário para um
Memory<byte>
. Isso é feito lendo os bytes do arquivo para um
array de bytes usando um FileStream
e,
em seguida, criando um Memory<byte>
a
partir desse array.
Em seguida, passamos o Memory<byte>
para a função ProcessarDados
, que
acessa os dados do Memory<byte>
como um
Span<byte>
. Isso permite que você trabalhe diretamente nos
bytes do arquivo sem copiá-los para outro local.
Dentro da função ProcessarDados
,
iteramos sobre os bytes do
Span<byte>
e aplicamos alguma lógica de processamento, neste caso, invertendo o valor de
cada byte. Os bytes processados são armazenados novamente no
Span<byte>
,
atualizando diretamente o Memory<byte>
.
Por fim, exibimos os dados processados chamando a função
ExibirDados
, que itera sobre o
Span<byte>
e imprime cada byte processado.
Este exemplo ilustra como o
Memory<T>
pode ser usado para processar grandes quantidades de dados, como em um arquivo,
sem a necessidade de copiar ou alocar memória adicional. Ele oferece uma maneira
eficiente e flexível de trabalhar com dados na memória.
Nota: Como sugestão segue o link da apresentação do recurso System.Threading.Channels que permite uma melhor implementação do recurso apresentando neste artigo.
E estamos conversados.
"Eu sou o Alfa e o Ômega, o princípio e o fim, o
primeiro e o derradeiro.
Bem-aventurados aqueles que guardam os seus
mandamentos, para que tenham direito à árvore da vida, e possam entrar na cidade
pelas portas."
Apocalipse 22:13-14
Referências: