Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
C#

C#: Use Span for High-Performance Memory Operations

- 22.03.26 - ErcanOPAK

🚀 Zero-Allocation String Operations

Substring creates new string. Array slicing copies memory. Span<T> provides zero-copy views. Massive performance gains.

The Problem with Substring

// ❌ Traditional: Allocates new strings
string data = "2024-03-19T10:30:00";
string year = data.Substring(0, 4);      // Allocates "2024"
string month = data.Substring(5, 2);     // Allocates "03"
string day = data.Substring(8, 2);       // Allocates "19"

// Process 1 million dates: 3 million allocations!
// Garbage collector works overtime

// ✅ Span: Zero allocations
ReadOnlySpan data = "2024-03-19T10:30:00";
ReadOnlySpan year = data.Slice(0, 4);   // No allocation
ReadOnlySpan month = data.Slice(5, 2);  // No allocation
ReadOnlySpan day = data.Slice(8, 2);    // No allocation

int yearNum = int.Parse(year);   // Parses directly from span

🎯 Span Basics

// Stack-allocated array (no heap allocation!)
Span numbers = stackalloc int[100];
numbers[0] = 42;

// View into existing array (no copy!)
int[] array = new int[100];
Span slice = array.AsSpan(10, 50);  // Elements 10-59
slice[0] = 100;  // Modifies original array

// String as ReadOnlySpan (immutable)
ReadOnlySpan text = "Hello, World!";
ReadOnlySpan hello = text.Slice(0, 5);

// Can't do this (compile error):
// Span span = "Hello";  // Strings are immutable

Real-World: CSV Parsing

// ❌ Traditional: Many allocations
public List ParseCSV(string csv)
{
    var people = new List();
    var lines = csv.Split('\n');  // Allocates array of strings
    
    foreach (var line in lines)
    {
        var parts = line.Split(',');  // Allocates array per line
        people.Add(new Person 
        { 
            Name = parts[0],  // Already allocated
            Age = int.Parse(parts[1]) 
        });
    }
    return people;
}

// ✅ Span: Minimal allocations
public List ParseCSVFast(ReadOnlySpan csv)
{
    var people = new List();
    
    while (csv.Length > 0)
    {
        int newline = csv.IndexOf('\n');
        var line = newline >= 0 ? csv.Slice(0, newline) : csv;
        csv = newline >= 0 ? csv.Slice(newline + 1) : default;
        
        int comma = line.IndexOf(',');
        var name = line.Slice(0, comma);
        var age = line.Slice(comma + 1);
        
        people.Add(new Person 
        { 
            Name = name.ToString(),  // Only allocation here
            Age = int.Parse(age) 
        });
    }
    return people;
}

// Benchmark: 10x faster, 90% less memory

stackalloc for Temp Buffers

// ❌ Allocates array on heap
byte[] buffer = new byte[256];
ReadData(buffer);

// ✅ Stack-allocated (no GC pressure)
Span buffer = stackalloc byte[256];
ReadData(buffer);

// Warning: Don't stackalloc large arrays!
// Stack is limited (typically 1MB)
// Use for small buffers only (< 1KB)

// Safe pattern
Span buffer = size <= 1024 
    ? stackalloc byte[size] 
    : new byte[size];

🔧 Memory<T> for Async

// Span can't be used in async methods (stack-based)
// Use Memory instead

public async Task ProcessAsync(Memory data)
{
    // Can hold across await
    await Task.Delay(100);
    
    // Convert to Span when needed
    Span span = data.Span;
    span[0] = 42;
}

// ReadOnlyMemory for immutable
public async Task CountAsync(ReadOnlyMemory text)
{
    await Task.Delay(100);
    return text.Span.Count('a');
}

✅ When to Use Span

  • String parsing: CSV, JSON, log files
  • Binary protocols: Network packets, file formats
  • Hot paths: Performance-critical loops
  • Large data processing: Avoid allocations
  • Temporary buffers: stackalloc for small sizes

⚠️ Limitations

  • Can't store in class fields: Span is stack-only (ref struct)
  • Can't use in async: Use Memory<T> instead
  • Can't box: No object, IEnumerable, etc.
  • Learning curve: Different mental model

"Log parser was allocating 500MB/second. Rewrote with Span<char>. Allocations down to 5MB/second. GC pauses gone. Throughput tripled. Span is game-changing for high-performance .NET."

— Performance Engineer

Related posts:

C#: Use Collection Expressions for Cleaner Collection Initialization

ConfigureAwait(false) — When It Actually Matters

Every Async Method Should Accept CancellationToken

Post Views: 6

Post navigation

C#: Async/Await Best Practices – Avoid Common Mistakes
Visual Studio: Use Live Unit Testing for Instant Test Feedback

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

April 2026
M T W T F S S
 12345
6789101112
13141516171819
20212223242526
27282930  
« Mar    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (950)
  • How to add default value for Entity Framework migrations for DateTime and Bool (858)
  • Get the First and Last Word from a String or Sentence in SQL (836)
  • How to select distinct rows in a datatable in C# (805)
  • How to make theater mode the default for Youtube (751)
  • Add Constraint to SQL Table to ensure email contains @ (578)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (564)
  • Average of all values in a column that are not zero in SQL (531)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (489)
  • Find numbers with more than two decimal places in SQL (447)

Recent Posts

  • C#: Use Init-Only Setters for Immutable Objects After Construction
  • C#: Use Expression-Bodied Members for Concise Single-Line Methods
  • C#: Enable Nullable Reference Types to Eliminate Null Reference Exceptions
  • C#: Use Record Types for Immutable Data Objects
  • SQL: Use CTEs for Readable Complex Queries
  • SQL: Use Window Functions for Advanced Analytical Queries
  • .NET Core: Use Background Services for Long-Running Tasks
  • .NET Core: Use Minimal APIs for Lightweight HTTP Services
  • Git: Use Cherry-Pick to Apply Specific Commits Across Branches
  • Git: Use Interactive Rebase to Clean Up Commit History Before Merge

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (950)
  • How to add default value for Entity Framework migrations for DateTime and Bool (858)
  • Get the First and Last Word from a String or Sentence in SQL (836)
  • How to select distinct rows in a datatable in C# (805)
  • How to make theater mode the default for Youtube (751)

Recent Posts

  • C#: Use Init-Only Setters for Immutable Objects After Construction
  • C#: Use Expression-Bodied Members for Concise Single-Line Methods
  • C#: Enable Nullable Reference Types to Eliminate Null Reference Exceptions
  • C#: Use Record Types for Immutable Data Objects
  • SQL: Use CTEs for Readable Complex Queries

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com