CodeGym /Courses /C# SELF /Memory<T> and ...

Memory<T> and ReadOnlyMemory<T>

C# SELF
Level 65 , Lesson 4
Available

1. Introduction

It all started because .NET folks wanted faster work with large volumes of data and to give developers a way to do that conveniently and safely. First came Span<T>, which is a "window" into a contiguous memory region, able to show part of an array, a string, or even memory allocated outside of .NET (for example via P/Invoke).

But Span<T> has one big restriction: it must always live on the stack. You can't store it in class fields, return it from methods, or pass it between async methods. The reason is safety: if someone keeps a reference to memory that no longer exists, the app will crash.

Sometimes you need to return slices from methods, store them in collections or class fields, or use them in asynchronous APIs. That's where Memory<T> comes in — essentially a safe "long-lived" version of Span<T> that can live on the heap, be passed between threads, sit in properties and objects, and behave like a normal .NET object.

There's also the "older brother" — ReadOnlyMemory<T>, which, as you might guess, doesn't allow modifying the underlying data but lets you read it wherever needed.

2. What's the difference between Span<T> and Memory<T>

Here's a small table to compare them visually:

Span<T>
Memory<T>
Where it lives Stack only Heap and stack (heap/stack)
Can be stored in a field ❌ No ✅ Yes
Can be returned from a method ❌ No ✅ Yes
Async/await methods ❌ Not allowed ✅ Allowed
Mutable ✅ There's also ReadOnlySpan<T> ✅ There's also ReadOnlyMemory<T>
Supports slicing ✅ Yes ✅ Yes

If you need to quickly iterate over data inside a method — use Span<T>. If you need to return the result or put it into a class field — use Memory<T>. And if the data is read-only — use ReadOnlyMemory<T>.

3. Signature and basic structure of Memory<T>

As usual: Memory<T> is generic. You can create Memory<int>, Memory<byte>, Memory<char>, even Memory<MyType>. Inside Memory<T> there is a reference to an array, string, or other data source, plus information about the range (start index and length).

To get fast access for processing from Memory<T>, use its Span property — this gives you a Span<T> you can use inside a synchronous method.

4. How to create Memory<T>: Practical examples

Example 1. Creating from an array

int[] numbers = { 1, 2, 3, 4, 5, 6 };
Memory<int> memory = new Memory<int>(numbers); // The whole array

// You can take a "slice" — part of the array
Memory<int> slice = memory.Slice(2, 3); // elements 2, 3 and 4

Example 2. Creating from a string (via Memory<char>)

string text = "Hello, world!";
Memory<char> charMemory = text.AsMemory(); // The whole text as memory
Memory<char> subMemory = charMemory.Slice(7, 3); // from 7th char, 3 chars ("world")

Example 3. Using ReadOnlyMemory<T>

Exactly the same, just protected from modification:

int[] data = { 10, 20, 30, 40 };
ReadOnlyMemory<int> readOnly = data; // Won't allow modifying via this object

5. Converting between Memory<T> and Span<T>

You can't work with Memory<T> as flexibly and as fast as with Span<T> directly — it's meant for a different purpose. But when you really need to process a chunk quickly, you can get an "instant" Span<T> from memory via the Span property:

void ProcessData(Memory<int> memory)
{
    Span<int> span = memory.Span;
    for (int i = 0; i < span.Length; i++)
    {
        span[i] += 100;
    }
}

Note: Span<T> only works inside the method. If you try to return it, the compiler will error.

With ReadOnlyMemory<T> it's the same, but you get a ReadOnlySpan<T> that prevents modification:

void PrintData(ReadOnlyMemory<int> memory)
{
    ReadOnlySpan<int> roSpan = memory.Span;
    foreach (var item in roSpan)
        Console.WriteLine(item);
}

6. Use in real tasks

Asynchronous data processing

This is where Memory<T> shines. You can use it in async methods! For example, async file reading:

using System.IO;
using System.Threading.Tasks;

public async Task ReadFileAsync(string path)
{
    byte[] buffer = new byte[4096];
    using var stream = File.OpenRead(path);
    int bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length));
    // Now you can work with buffer
}

Here AsMemory passes the buffer straight into the async method, and there are no scope or memory-lifetime issues like there would be with Span<T>.

Storing data slices in properties and fields

Sometimes you need a class that holds a "piece" of a large array for later use:

class DataChunk
{
    public Memory<byte> Data { get; }

    public DataChunk(Memory<byte> data)
    {
        Data = data;
    }
}

7. Working with collections, strings and arrays

With arrays

Most common:

byte[] bytes = { 1, 2, 3, 4, 5 };
Memory<byte> mem = bytes;          // Whole array
Memory<byte> part = mem.Slice(2);  // From the third element to the end

With strings

Via AsMemory():

string hello = "Hello, Memory!";
ReadOnlyMemory<char> mem = hello.AsMemory(6, 6); // "Memory"

With collections (like List<T>)

You can't create Memory<T> from a List<T> directly. Only via an array:

List<int> list = new List<int> { 1, 2, 3 };
Memory<int> mem = list.ToArray(); // A copy, not a reference!

Be careful: if you want to avoid copying — keep the data in an array.

8. Common mistakes when working with Memory<T>

Mistake #1: trying to use Span<T> instead of Memory<T> in class fields. You can't store Span<T> in class fields because it's tied to the stack. The compiler will error. Use Memory<T> to store data on the heap.

Mistake #2: expecting copying when slicing. Memory<T> doesn't copy data — it creates a "window" into the existing array. If you change data via one Memory<T>, it'll affect all others referencing the same memory.

Mistake #3: trying to create Memory<T> from List<T> directly. Memory<T> works only with arrays because List<T> can move its data in memory. Convert the list to an array via ToArray().

Mistake #4: ignoring ReadOnlyMemory<T> for immutable data. If the data doesn't need modifications, use ReadOnlyMemory<T> instead of Memory<T> for better safety.

2
Task
C# SELF, level 65, lesson 4
Locked
Asynchronous Work with Memory
Asynchronous Work with Memory
1
Survey/quiz
Memory in C#, level 65, lesson 4
Unavailable
Memory in C#
How memory works in .NET
Comments (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Marian Level 4, Poznan, Poland
12 August 2026
The example: string hello = "Hello, Memory!"; ReadOnlyMemory<char> mem = hello.AsMemory(6, 6); // "Memory" -> there should be hello.AsMemory(7,6) to get "Memory"