This is a perfect demo topic. Here’s a single, self-contained console app that lets you feel the difference between:
- 🚫 Without
Span<T>– usingSubstring(allocates new strings every time) - ✅ With
Span<T>– usingAsSpan+Slice(no extra allocations)
You’ll get timing + GC stats for both in the same run.
1️⃣ Full Code – Program.cs
Copy–paste this as Program.cs (or replace the existing one in a new console app):
2️⃣ (Optional) Minimal .csproj
If you want a fully explicit project file, create SpanDemo.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Code language: HTML, XML (xml)
You can change net8.0 to net9.0 or net10.0 depending on your installed SDK.
3️⃣ Step-by-Step: How to Run It
Step 1 – Create a new console app
dotnet new console -n SpanDemo
cd SpanDemo
Code language: JavaScript (javascript)
Step 2 – Replace Program.cs
- Open
Program.cs - Delete everything
- Paste the full code from section 1️⃣
(Optionally replace the generated .csproj with the one in 2️⃣.)
Step 3 – Build and run in Release
dotnet run -c Release
You’ll see output similar to:
=========================================
Span<T> Demo – Substring vs Span
=========================================
Items to process: 500,000
Preparing test data...
Warming up (small runs)...
--- Warmup – Substring (no Span) ---
Items processed : 50,000
Time Elapsed : 45 ms
GC Gen0 : 5
GC Gen1 : 0
GC Gen2 : 0
Managed Memory Δ: 2.30 MB
Checksum (ignore, just prevents JIT from optimizing away work): 1234567
--- Warmup – Span<T> ---
Items processed : 50,000
Time Elapsed : 20 ms
GC Gen0 : 1
GC Gen1 : 0
GC Gen2 : 0
Managed Memory Δ: 0.20 MB
Checksum (ignore, just prevents JIT from optimizing away work): 1234567
=========== REAL TESTS (Release) ==========
--- WITHOUT Span<T> – using Substring (allocations) ---
Items processed : 500,000
Time Elapsed : 400 ms
GC Gen0 : 40
GC Gen1 : 2
GC Gen2 : 0
Managed Memory Δ: 20.50 MB
Checksum (ignore, just prevents JIT from optimizing away work): 1234567
--- WITH Span<T> – using AsSpan + Slice (no extra allocations) ---
Items processed : 500,000
Time Elapsed : 180 ms
GC Gen0 : 4
GC Gen1 : 0
GC Gen2 : 0
Managed Memory Δ: 1.10 MB
Checksum (ignore, just prevents JIT from optimizing away work): 1234567
Code language: HTML, XML (xml)
(Your exact numbers will vary by machine, but the pattern should be similar.)
4️⃣ How to “Experience” and Interpret the Results
Look at these metrics for each scenario:
- ⏱ Time Elapsed
- 🗑 GC Gen0 / Gen1 / Gen2
- 💾 Managed Memory Δ (MB)
🔴 Scenario 1 – WITHOUT Span<T> (using Substring)
What happens:
- For each of the 500,000 strings, we call
Substringthree times:- Each
Substringcreates a new string allocation on the heap. - So we allocate 1.5M strings in the loop.
- Each
You’ll typically see:
- Higher elapsed time
- Many more Gen0 collections
- Possibly some Gen1/Gen2 collections
- Larger Managed Memory Δ
This simulates a typical string parsing / text processing pattern that is allocation-heavy.
🟢 Scenario 2 – WITH Span<T>
What happens:
- We still have the same 500,000 original strings (same cost as before).
- But inside the loop we use:
ReadOnlySpan<char> span = s.AsSpan(); var part1 = span.Slice(0, 8); var part2 = span.Slice(9, 3); var part3 = span.Slice(span.Length - 10); - These are just views (windows) over the same underlying string:
- No new string objects are created.
- No extra heap allocations per slice.
You should see:
- Lower elapsed time (less allocation + less GC work)
- Much fewer Gen0 collections
- Gen1/Gen2 often drop to zero
- Managed Memory Δ is much smaller
This is exactly the benefit of Span<T>:
🔹 Operate on slices of data without additional allocations.
5️⃣ Playing with the Load
To make the impact more dramatic (for demo):
- Increase item count:
const int itemCount = 1_000_000; - Or add more slicing and comparisons per string.
Just be aware that very large values can make the Substring scenario quite heavy.
6️⃣ How to Explain This in Training
You can summarize it like this:
- Without
Span<T>, everySubstringcall allocates a new string. In a tight loop, this leads to tons of small GC’ed objects, more GC cycles, and higher latency.- With
Span<T>, we useAsSpan+Sliceto work with slices of the existing string. No new allocations → lower GC pressure → better throughput.
I’m a DevOps/SRE/DevSecOps/Cloud Expert passionate about sharing knowledge and experiences. I have worked at Cotocus. I share tech blog at DevOps School, travel stories at Holiday Landmark, stock market tips at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow , and SEO strategies at Wizbrand.
Do you want to learn Quantum Computing?
Please find my social handles as below;
Rajesh Kumar Personal Website
Rajesh Kumar at YOUTUBE
Rajesh Kumar at INSTAGRAM
Rajesh Kumar at X
Rajesh Kumar at FACEBOOK
Rajesh Kumar at LINKEDIN
Rajesh Kumar at WIZBRAND