Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Udostępnij Udostępnij Kontakt
Wprowadzenie

Performance-critical code zawsze walczył z alokacjami w heap - każda alokacja to praca dla garbage collector. W 2015 roku nie miałeś wyboru - wszystko na heapie. W 2026 roku masz Span<T> i Memory<T> - zero-allocation code!

W tym wpisie poznasz Span<T> - stack-based slice of memory, Memory<T> - heap-safe alternative, stackalloc, implicit span conversions (C# 14), i praktyczne zastosowania w string parsing, data processing, i high-performance scenarios!

📅 Timeline - ewolucja Span i Memory
  • C# 1.0-6.0 (do 2015) - Tylko heap allocations, brak alternatywy
  • C# 7.2 (2017) - 🔥 Span<T> i Memory<T> introduced!
  • C# 7.2 (2017) - stackalloc z Span<T>
  • C# 8.0 (2019) - Ranges i indices z Span
  • C# 11 (2022) - Improved span patterns
  • C# 14 (2026) - 🔥 Implicit span conversions
Problem - heap allocations wszędzie

Alokacje w heap - performance killer

// Problem: każda operacja alokuje w heap!
string text = "Hello World from C#";

// Substring - alokacja!
string sub1 = text.Substring(0, 5);     // "Hello" - nowy string w heap
string sub2 = text.Substring(6, 5);     // "World" - nowy string w heap

// Split - wiele alokacji!
string[] words = text.Split(' ');       // Array + każdy string = 5 alokacji!

// ToUpper - alokacja!
string upper = text.ToUpper();          // Nowy string w heap

// W pętli to katastrofa:
for (int i = 0; i < 1000000; i++)
{
    string s = text.Substring(0, 5);    // 1 milion alokacji! 😱
    // Garbage collector: "Am I a joke to you?"
}

Konsekwencje heap allocations

// Przykład: CSV parsing
string csv = "Jan,Kowalski,30,Warsaw\nAnna,Nowak,25,Krakow";

// ❌ Stare podejście - wiele alokacji
string[] lines = csv.Split('\n');           // Alokacja array + strings
foreach (string line in lines)
{
    string[] fields = line.Split(',');      // Alokacja array + strings
    string name = fields[0];                 // Reference
    string surname = fields[1];              // Reference
    int age = int.Parse(fields[2]);         // Parse
    string city = fields[3];                 // Reference
    
    // Dla 2 linii: 2 + 2*4 = 10 alokacji w heap!
}

// Konsekwencje:
// 1. Memory pressure - GC musi sprzątać
// 2. GC pauses - zatrzymanie aplikacji
// 3. CPU cycles - GC zabiera czas CPU
// 4. Cache misses - heap nie jest cache-friendly
🔍 Stack vs Heap

Stack - szybki, automatyczne cleanup, ograniczony rozmiar (~1MB)
Heap - wolniejszy, wymaga GC, praktycznie nieograniczony

Span<T> pozwala używać stack dla operacji które normalnie wymagałyby heap!

🔥 Span<T> - zero-allocation slices (C# 7.2)

Czym jest Span<T>?

🎉 C# 7.2 - Span<T>

Span<T> to "view" na ciągły obszar pamięci - może wskazywać na stack, heap, lub native memory. Zero allocations!

// Span<T> - slice of memory
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Span wskazuje na część array - BEZ alokacji!
Span<int> slice = numbers.AsSpan(2, 5);  // [3, 4, 5, 6, 7]

// Modyfikacja przez Span modyfikuje oryginalny array
slice[0] = 99;
Console.WriteLine(numbers[2]);  // 99 - zmienione!

// Span może wskazywać na:
// 1. Array (heap)
Span<int> fromArray = numbers.AsSpan();

// 2. Stack (stackalloc)
Span<int> fromStack = stackalloc int[10];

// 3. Native memory (unsafe)
// Span<byte> fromNative = new Span<byte>(pointer, length);

Span<T> vs array - performance

❌ Array slicing - alokacje
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Kopiuje elementy - alokacja!
int[] slice = numbers[2..7];  // [3, 4, 5, 6, 7]

// Nowy array w heap
// GC będzie musiał to posprzątać
✅ Span slicing - zero allocations
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Wskazuje na część array - BEZ alokacji!
Span<int> slice = numbers.AsSpan(2, 5);

// Tylko referencja do pamięci
// Brak GC pressure! ✨

Span<T> operations

// Span operations - wszystkie bez alokacji!
int[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Span<int> span = data.AsSpan();

// Length
int length = span.Length;  // 10

// Indexing
int first = span[0];       // 1
int last = span[^1];       // 10 (index from end)

// Slicing z range operator
Span<int> slice1 = span[2..5];    // [3, 4, 5]
Span<int> slice2 = span[..3];     // [1, 2, 3]
Span<int> slice3 = span[5..];     // [6, 7, 8, 9, 10]

// Clear - zerowanie
span.Clear();  // Wszystkie elementy = 0

// Fill - wypełnienie wartością
span.Fill(42);  // Wszystkie elementy = 42

// CopyTo - kopiowanie
int[] destination = new int[10];
span.CopyTo(destination);

// Slice method
Span<int> sliceMethod = span.Slice(2, 5);  // [3, 4, 5]

Span<T> w foreach

// Span w foreach - bez alokacji!
int[] numbers = { 1, 2, 3, 4, 5 };
Span<int> span = numbers.AsSpan();

// foreach przez Span - bez boxing, bez allocations
foreach (int num in span)
{
    Console.WriteLine(num);
}

// Można też modyfikować przez ref
foreach (ref int num in span)
{
    num *= 2;  // Modyfikuje oryginalny array!
}

Console.WriteLine(string.Join(", ", numbers));  // 2, 4, 6, 8, 10
stackalloc i Span<T> - stack allocations

stackalloc przed C# 7.2 - unsafe!

// Przed C# 7.2 - stackalloc wymaga unsafe context
unsafe
{
    int* buffer = stackalloc int[10];  // Stack allocation
    
    // Użycie pointer arithmetic
    for (int i = 0; i < 10; i++)
    {
        buffer[i] = i * 2;
    }
}

// Problemy:
// 1. Wymaga unsafe context
// 2. Pointer arithmetic - error-prone
// 3. Brak bounds checking
// 4. Trudne w użyciu

stackalloc z Span<T> - safe i wygodne!

🎉 C# 7.2 - stackalloc z Span

Span<T> span = stackalloc T[size] - stack allocation BEZ unsafe!

// C# 7.2+ - stackalloc z Span (safe!)
Span<int> buffer = stackalloc int[10];  // Stack allocation - safe!

// Użycie jak normalny array - bounds checking!
for (int i = 0; i < buffer.Length; i++)
{
    buffer[i] = i * 2;
}

// buffer[10] = 42;  // ❌ IndexOutOfRangeException - bounds checking!

// Zalety:
// ✅ Stack allocation (szybkie)
// ✅ Brak GC pressure
// ✅ Safe - bounds checking
// ✅ Automatyczne cleanup (stack unwind)

Kiedy używać stackalloc?

// ✅ DOBRZE - małe buffery (< 1KB)
void ProcessData()
{
    Span<byte> buffer = stackalloc byte[256];  // 256 bytes - OK
    
    // Użyj buffer
    FillBuffer(buffer);
}

// ✅ DOBRZE - temporary buffers
void ParseNumbers(string input)
{
    Span<int> numbers = stackalloc int[10];  // Temporary
    
    // Parse do buffer
    int count = ParseIntoSpan(input, numbers);
}

// ⚠️ UWAŻAJ - duże buffery
void BadExample()
{
    // Span<byte> huge = stackalloc byte[1024 * 1024];  // 1MB - StackOverflowException!
    
    // Stack ma ~1MB - nie alokuj dużo na raz!
}

// Zasada: stackalloc dla bufferów < 1KB
// Dla większych - użyj array albo ArrayPool

stackalloc - praktyczne przykłady

// Przykład 1: String parsing bez alokacji
void ParseCoordinates(string input)
{
    // input = "10,20,30,40"
    Span<int> coords = stackalloc int[4];  // Stack buffer
    
    int index = 0;
    int current = 0;
    
    foreach (char c in input)
    {
        if (c == ',')
        {
            coords[index++] = current;
            current = 0;
        }
        else
        {
            current = current * 10 + (c - '0');
        }
    }
    coords[index] = current;
    
    // coords ma [10, 20, 30, 40] - zero heap allocations!
}

// Przykład 2: Byte manipulation
void ProcessBytes(byte[] data)
{
    Span<byte> temp = stackalloc byte[16];  // 16-byte buffer
    
    // Copy first 16 bytes
    data.AsSpan(0, 16).CopyTo(temp);
    
    // Process temp buffer
    for (int i = 0; i < temp.Length; i++)
    {
        temp[i] ^= 0xFF;  // XOR with 0xFF
    }
    
    // Copy back
    temp.CopyTo(data.AsSpan(0, 16));
}

// Przykład 3: Hash computation
int ComputeSimpleHash(string text)
{
    Span<int> hashes = stackalloc int[4];  // 4 hash buckets
    
    foreach (char c in text)
    {
        int bucket = c % 4;
        hashes[bucket] += c;
    }
    
    int result = 0;
    foreach (int h in hashes)
    {
        result ^= h;
    }
    
    return result;
}
ReadOnlySpan<T> - immutable view

ReadOnlySpan<T> - read-only access

// ReadOnlySpan<T> - nie można modyfikować
int[] numbers = { 1, 2, 3, 4, 5 };
ReadOnlySpan<int> span = numbers;

// Odczyt - OK
int first = span[0];  // 1

// Modyfikacja - BŁĄD!
// span[0] = 99;  // ❌ BŁĄD kompilacji - readonly!

// Użycie: gdy nie chcesz pozwolić na modyfikację
void PrintNumbers(ReadOnlySpan<int> numbers)
{
    foreach (int num in numbers)
    {
        Console.WriteLine(num);
    }
    
    // numbers[0] = 99;  // ❌ BŁĄD - nie można modyfikować
}

ReadOnlySpan dla string - zero allocations!

// String jako ReadOnlySpan<char> - POTĘŻNE!
string text = "Hello World from C#";

// AsSpan - view na string bez alokacji
ReadOnlySpan<char> span = text.AsSpan();

// Slicing bez alokacji!
ReadOnlySpan<char> hello = span[0..5];      // "Hello" - BEZ alokacji!
ReadOnlySpan<char> world = span[6..11];     // "World" - BEZ alokacji!

// ToString() tylko gdy potrzebujesz string
string helloStr = hello.ToString();  // Teraz alokacja (gdy potrzeba)

// Porównanie bez alokacji
bool equals = hello.SequenceEqual("Hello");  // true - bez alokacji!

// StartsWith/EndsWith bez alokacji
bool starts = span.StartsWith("Hello");  // true
bool ends = span.EndsWith("C#");         // true
❌ String operacje - alokacje
string text = "Hello World from C#";

// Każda operacja = alokacja!
string hello = text.Substring(0, 5);     // Alokacja
string world = text.Substring(6, 5);     // Alokacja
bool starts = text.StartsWith("Hello"); // OK
bool equals = hello == "Hello";          // OK

// W pętli:
for (int i = 0; i < 100000; i++)
{
    string sub = text.Substring(0, 5);
    // 100k alokacji! 😱
}
✅ ReadOnlySpan - zero allocations
string text = "Hello World from C#";
ReadOnlySpan<char> span = text.AsSpan();

// Zero alokacji!
ReadOnlySpan<char> hello = span[0..5];
ReadOnlySpan<char> world = span[6..11];
bool starts = span.StartsWith("Hello");
bool equals = hello.SequenceEqual("Hello");

// W pętli:
for (int i = 0; i < 100000; i++)
{
    ReadOnlySpan<char> sub = span[0..5];
    // Zero alokacji! ✨
}
🔥 Implicit Span Conversions (C# 14)

Problem przed C# 14

// Przed C# 14 - explicit conversions
void ProcessData(Span<int> data)
{
    // ...
}

int[] array = { 1, 2, 3, 4, 5 };

// Musisz explicit .AsSpan()
ProcessData(array.AsSpan());  // Verbose

// Lub Span constructor
ProcessData(new Span<int>(array));  // Jeszcze bardziej verbose

C# 14 - Implicit conversions!

🎉 C# 14 - Implicit Span Conversions

Array → Span<T> automatycznie! Brak .AsSpan() potrzebne!

// C# 14 - implicit conversions!
void ProcessData(Span<int> data)
{
    foreach (ref int num in data)
    {
        num *= 2;
    }
}

int[] array = { 1, 2, 3, 4, 5 };

// ✅ Przekazanie bezpośrednio - implicit conversion!
ProcessData(array);  // Automatycznie array → Span<int>!

// Działa też dla ReadOnlySpan
void PrintData(ReadOnlySpan<int> data)
{
    foreach (int num in data)
    {
        Console.WriteLine(num);
    }
}

PrintData(array);  // Implicit conversion! ✨

Implicit conversions - więcej przykładów

// Implicit conversions dla różnych typów
void ProcessBytes(Span<byte> bytes) { /* ... */ }
void ProcessChars(ReadOnlySpan<char> chars) { /* ... */ }
void ProcessInts(Span<int> ints) { /* ... */ }

byte[] byteArray = { 1, 2, 3 };
char[] charArray = { 'a', 'b', 'c' };
int[] intArray = { 1, 2, 3 };

// Wszystkie implicit!
ProcessBytes(byteArray);   // byte[] → Span<byte>
ProcessChars(charArray);   // char[] → ReadOnlySpan<char>
ProcessInts(intArray);     // int[] → Span<int>

// String → ReadOnlySpan<char> też implicit!
string text = "Hello";
ProcessChars(text);  // Implicit conversion! ✨

Overloading z Span - backward compatibility

// C# 14 - możesz mieć overloads dla array i Span
class DataProcessor
{
    // Stary API - backward compatibility
    public void Process(int[] data)
    {
        Console.WriteLine("Array overload");
        Process(data.AsSpan());  // Deleguj do Span version
    }
    
    // Nowy API - zero allocations
    public void Process(Span<int> data)
    {
        Console.WriteLine("Span overload");
        foreach (ref int num in data)
        {
            num *= 2;
        }
    }
}

var processor = new DataProcessor();
int[] array = { 1, 2, 3 };

// C# 14 wywołuje Span overload dzięki implicit conversion!
processor.Process(array);  // "Span overload" ✨
Memory<T> - heap-safe alternative

Problem ze Span<T> - tylko stack

// Span<T> ma ograniczenie - ref struct
// NIE może być:
// 1. Polem w klasie
// 2. Używany w async/await
// 3. Stored w heap

// ❌ To NIE działa
class Container
{
    // private Span<int> _data;  // ❌ BŁĄD - Span nie może być polem!
}

// ❌ To też NIE działa
async Task ProcessAsync(Span<int> data)  // ❌ BŁĄD - Span w async!
{
    await Task.Delay(100);
    // ...
}

Memory<T> - rozwiązanie!

🎉 C# 7.2 - Memory<T>

Memory<T> to heap-safe wrapper wokół Span<T> - może być przechowywany, async-friendly!

// Memory<T> - może być wszędzie!
class Container
{
    private Memory<int> _data;  // ✅ OK - Memory może być polem!
    
    public Container(int[] array)
    {
        _data = array;  // Implicit conversion
    }
    
    public void Process()
    {
        Span<int> span = _data.Span;  // Get Span when needed
        
        foreach (ref int num in span)
        {
            num *= 2;
        }
    }
}

// ✅ Memory w async - działa!
async Task ProcessAsync(Memory<int> data)
{
    await Task.Delay(100);
    
    // Get Span when needed
    Span<int> span = data.Span;
    foreach (ref int num in span)
    {
        num *= 2;
    }
}

Memory vs Span - kiedy czego używać?

Feature Span<T> Memory<T>
Gdzie można użyć Stack only (local variables) Stack + Heap (fields, async)
Performance Fastest - no overhead Tiny overhead
Może być polem w klasie? ❌ Nie ✅ Tak
Async/await? ❌ Nie ✅ Tak
Get Span - .Span property
Kiedy używać Synchroniczne, lokalne operacje Async, storing, passing around
// Zasada: Span w metodach, Memory w klasach/async
class DataBuffer
{
    private Memory<byte> _buffer;  // Memory - może być polem
    
    public DataBuffer(int size)
    {
        _buffer = new byte[size];
    }
    
    // Synchroniczna metoda - używa Span
    public void Process()
    {
        Span<byte> span = _buffer.Span;
        // Fast processing z Span
    }
    
    // Async metoda - używa Memory
    public async Task ProcessAsync()
    {
        await Task.Delay(100);
        
        Span<byte> span = _buffer.Span;  // Get Span when needed
        // Processing
    }
}
String Parsing z Span - praktyczne przykłady

CSV parsing - zero allocations

// CSV parsing bez alokacji!
void ParseCSVLine(ReadOnlySpan<char> line, Span<int> output)
{
    int outputIndex = 0;
    int current = 0;
    bool inNumber = false;
    
    foreach (char c in line)
    {
        if (c >= '0' && c <= '9')
        {
            current = current * 10 + (c - '0');
            inNumber = true;
        }
        else if (c == ',' && inNumber)
        {
            output[outputIndex++] = current;
            current = 0;
            inNumber = false;
        }
    }
    
    if (inNumber)
    {
        output[outputIndex] = current;
    }
}

// Użycie
string csv = "10,20,30,40,50";
Span<int> numbers = stackalloc int[5];  // Stack buffer

ParseCSVLine(csv.AsSpan(), numbers);  // Zero heap allocations!

foreach (int num in numbers)
{
    Console.WriteLine(num);  // 10, 20, 30, 40, 50
}

String splitting z Span

// String splitting bez alokacji
void ProcessWords(ReadOnlySpan<char> text)
{
    Span<Range> ranges = stackalloc Range[10];  // Max 10 words
    int count = text.Split(ranges, ' ');
    
    for (int i = 0; i < count; i++)
    {
        ReadOnlySpan<char> word = text[ranges[i]];
        
        // Process word bez alokacji
        Console.WriteLine(word.ToString());
    }
}

string sentence = "Hello World from C# Span";
ProcessWords(sentence);  // Zero allocations dla splitting!

Number parsing z Span

// Number parsing z Span - zero allocations
bool TryParseInt(ReadOnlySpan<char> text, out int result)
{
    result = 0;
    bool negative = false;
    int start = 0;
    
    // Check for negative
    if (text.Length > 0 && text[0] == '-')
    {
        negative = true;
        start = 1;
    }
    
    for (int i = start; i < text.Length; i++)
    {
        char c = text[i];
        
        if (c < '0' || c > '9')
            return false;
        
        result = result * 10 + (c - '0');
    }
    
    if (negative)
        result = -result;
    
    return true;
}

// Użycie
string input = "12345";
ReadOnlySpan<char> span = input.AsSpan();

if (TryParseInt(span, out int number))
{
    Console.WriteLine(number);  // 12345 - zero allocations!
}

// .NET też ma Span-based parsing!
if (int.TryParse(span, out int num2))
{
    Console.WriteLine(num2);  // Built-in Span support!
}

Path parsing z Span

// Path parsing - zero allocations
void ParsePath(ReadOnlySpan<char> path)
{
    // Find last slash
    int lastSlash = path.LastIndexOf('/');
    
    if (lastSlash == -1)
        lastSlash = path.LastIndexOf('\\');
    
    if (lastSlash != -1)
    {
        ReadOnlySpan<char> directory = path[..lastSlash];
        ReadOnlySpan<char> filename = path[(lastSlash + 1)..];
        
        Console.WriteLine($"Dir: {directory.ToString()}");
        Console.WriteLine($"File: {filename.ToString()}");
        
        // Find extension
        int lastDot = filename.LastIndexOf('.');
        if (lastDot != -1)
        {
            ReadOnlySpan<char> name = filename[..lastDot];
            ReadOnlySpan<char> ext = filename[(lastDot + 1)..];
            
            Console.WriteLine($"Name: {name.ToString()}");
            Console.WriteLine($"Ext: {ext.ToString()}");
        }
    }
}

string path = "/home/user/documents/file.txt";
ParsePath(path);
// Dir: /home/user/documents
// File: file.txt
// Name: file
// Ext: txt
// Zero allocations podczas parsowania!
Practical Use Cases - kiedy używać Span

Use Case 1: High-performance parsing

// JSON parsing z Span (conceptual)
class JsonParser
{
    public void ParseObject(ReadOnlySpan<char> json)
    {
        Span<Range> properties = stackalloc Range[20];
        
        // Parse properties bez alokacji
        int count = ExtractProperties(json, properties);
        
        for (int i = 0; i < count; i++)
        {
            ReadOnlySpan<char> prop = json[properties[i]];
            ProcessProperty(prop);
        }
    }
}

Use Case 2: Cryptography i hashing

// SHA256 z Span - zero allocations
void ComputeHash(ReadOnlySpan<byte> data, Span<byte> hash)
{
    using var sha256 = System.Security.Cryptography.SHA256.Create();
    
    // Compute hash bez alokacji
    sha256.TryComputeHash(data, hash, out _);
}

// Użycie
byte[] data = new byte[1024];
Span<byte> hash = stackalloc byte[32];  // SHA256 = 32 bytes

ComputeHash(data, hash);  // Zero allocations!

Use Case 3: Binary protocol parsing

// Binary protocol - zero allocations
readonly struct PacketHeader
{
    public byte Version { get; init; }
    public byte Type { get; init; }
    public ushort Length { get; init; }
}

PacketHeader ParseHeader(ReadOnlySpan<byte> data)
{
    return new PacketHeader
    {
        Version = data[0],
        Type = data[1],
        Length = (ushort)(data[2] | (data[3] << 8))
    };
}

// Network packet processing
void ProcessPacket(ReadOnlySpan<byte> packet)
{
    var header = ParseHeader(packet[..4]);
    ReadOnlySpan<byte> payload = packet[4..(4 + header.Length)];
    
    // Process payload bez alokacji
    ProcessPayload(header.Type, payload);
}

Use Case 4: Image processing

// Image processing z Span
void FlipImage(Span<byte> pixels, int width, int height)
{
    int bytesPerRow = width * 4;  // RGBA = 4 bytes per pixel
    Span<byte> temp = stackalloc byte[bytesPerRow];
    
    for (int y = 0; y < height / 2; y++)
    {
        int topRow = y * bytesPerRow;
        int bottomRow = (height - 1 - y) * bytesPerRow;
        
        Span<byte> top = pixels.Slice(topRow, bytesPerRow);
        Span<byte> bottom = pixels.Slice(bottomRow, bytesPerRow);
        
        // Swap rows bez alokacji
        top.CopyTo(temp);
        bottom.CopyTo(top);
        temp.CopyTo(bottom);
    }
}
💡 Kiedy używać Span<T>?
  • Performance-critical code - parsing, serialization, crypto
  • String slicing - substring bez alokacji
  • Buffer manipulation - byte arrays, image processing
  • Hot paths - kod wykonywany miliony razy
  • Low allocation requirements - real-time systems
  • ❌ NIE używaj dla zwykłego business logic - overkill
  • ❌ NIE używaj gdy alokacje nie są problemem
Podsumowanie

Span<T> i Memory<T> - zero-allocation programming!

  • Span<T> (C# 7.2) - view na pamięć, zero allocations
  • stackalloc z Span - stack allocation bez unsafe
  • ReadOnlySpan<T> - immutable view, string slicing
  • Memory<T> - heap-safe wrapper, async-friendly
  • 🔥 Implicit conversions (C# 14) - array → Span automatic
  • String parsing z Span - CSV, splitting, numbers bez alokacji
  • Practical use cases - parsing, crypto, binary protocols, images
  • Performance - eliminacja GC pressure, cache-friendly

W kolejnym wpisie poznamy programowanie anynchroniczne, tj. ASYNC/AWAIT - głębokie zrozumienie, ValueTask, ConfigureAwait, cancellation tokens!

Zadanie dla Ciebie 🎯

Stwórz high-performance CSV parser używając Span<T>:

// Interface
interface ICsvParser
{
    // Parse CSV line do Span<T> bez alokacji
    int ParseLine<T>(ReadOnlySpan<char> line, Span<T> output) 
        where T : IParsable<T>;
}

// Przykład użycia:
string csvLine = "10,20,30,40,50";
Span<int> numbers = stackalloc int[10];

int count = parser.ParseLine(csvLine, numbers);
// numbers ma [10, 20, 30, 40, 50], count = 5
// Zero heap allocations!

Wymagania:

  1. Wszystkie operacje na Span/ReadOnlySpan - zero allocations
  2. Obsługa różnych separatorów (, ; \t)
  3. Obsługa quoted values: "value,with,comma"
  4. Generic - działa dla int, double, decimal, etc. (IParsable)
  5. Stackalloc buffer dla temporary work
🎯 BONUS: Zero-Allocation Text Protocol Parser

Stwórz parser dla custom text protocol używając TYLKO Span/stackalloc!

Protocol format:

COMMAND arg1 arg2 arg3\n
GET user/123\n
POST product name=Laptop price=2999 stock=10\n
DELETE order/456\n

Do zaimplementowania:

  1. Message parser:
    • Parse command (GET, POST, DELETE)
    • Parse path/resource
    • Parse key=value arguments
    • WSZYSTKO na Span/ReadOnlySpan
  2. Zero allocations:
    • Stackalloc dla temporary buffers
    • ReadOnlySpan dla slicing
    • Brak string.Split, Substring
  3. Performance testing:
    • Parse 1 million messages
    • Measure allocations (GC.GetTotalMemory)
    • Compare z naïve string-based parser
  4. Builder pattern z Span:
    • Build response messages bez alokacji
    • Span<char> output buffer

Przykład użycia:

string message = "POST product name=Laptop price=2999 stock=10";
ReadOnlySpan<char> span = message.AsSpan();

// Parse command
var (command, rest) = ParseCommand(span);  // "POST", "product name=..."

// Parse resource
var (resource, args) = ParseResource(rest);  // "product", "name=..."

// Parse arguments
Span<KeyValue> kvPairs = stackalloc KeyValue[10];
int count = ParseArguments(args, kvPairs);  // Zero allocations!

// kvPairs ma:
// [0]: Key="name", Value="Laptop"
// [1]: Key="price", Value="2999"
// [2]: Key="stock", Value="10"

Console.WriteLine($"Command: {command.ToString()}");
Console.WriteLine($"Resource: {resource.ToString()}");
for (int i = 0; i < count; i++)
{
    Console.WriteLine($"{kvPairs[i].Key.ToString()} = {kvPairs[i].Value.ToString()}");
}

Ten projekt demonstruje prawdziwy high-performance parsing z Span<T> - zero allocations, stackalloc, protocol parsing! 🚀⚡