In this lesson, you will learn how to leverage Memory<T> and Span<T> to achieve zero-allocation data transfers between C# and JavaScript, significantly boosting your Blazor WebAssembly performance.
The Problem with Standard Interop
In standard Blazor interop, passing large buffers of data—like raw image pixels or complex binary telemetry—often triggers hidden allocations. When you pass a byte array to JavaScript, the framework typically creates a copy of that data to bridge the memory barrier between the .NET runtime and the browser’s JavaScript engine. Frequent copying of large datasets puts pressure on the Garbage Collector (GC), leading to "stutters" or frame drops in your UI.
The Span-Based Solution
Instead of allocating a new array or base64-encoding your data, we use Memory<T> and Span<T>. When you use the IJSRuntime.InvokeVoidAsync (or similar) methods with Memory<T>, you are essentially providing the interop layer with a window into existing memory.
By utilizing Memory<byte> inside a DotNetStreamReference, we can stream data directly from .NET memory to JavaScript without duplicating the underlying buffer.
// High-performance pattern: Renting from the array pool to avoid allocations
var buffer = ArrayPool<byte>.Shared.Rent(4096);
try
{
// Use Memory<byte> to create a view of the buffer
var data = new Memory<byte>(buffer, 0, bytesRead);
// Efficiently stream the view without full object serialization
using var stream = new DotNetStreamReference(new MemoryStream(data.ToArray()));
await JS.InvokeVoidAsync("processData", stream);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
Why This Matters
By keeping data in Span or Memory form, you avoid the overhead of the serializer trying to map objects to JSON. You aren't just moving data; you are pointing the JavaScript engine toward the memory location already allocated by the .NET runtime. This minimizes the footprint of your interop calls, ensuring that high-frequency updates—like real-time charting or data processing—remain buttery smooth even under heavy loads.
Key Takeaway: Use Memory<T> and DotNetStreamReference to pass existing memory buffers to JavaScript, effectively bypassing expensive serialization and heap allocations.