September 19, 2025

LINQ vs Span Overload Surprises

Photo Credit: Dall-E by OpenAI

When I tried building some code in the Visual Studio 2026 Insiders build, something strange happened:
perfectly valid code in VS 2022 started failing.

After digging in, I realized it wasn’t a bug in my code — it was a change in compiler overload resolution.


The LINQ vs Span Problem

Consider this code:

var endPart = request.SearchTerm.Split(" ")
                                .Reverse()
                                .FirstOrDefault();

✅ In Visual Studio 2022

  • Split() returns string[].
  • .Reverse() resolves to Enumerable.Reverse(IEnumerable&lt;T&gt;), which returns IEnumerable<T>.
  • .FirstOrDefault() works.

❌ In Visual Studio 2026 Insiders

  • string[] is implicitly convertible to Span<string>.
  • .Reverse() resolves to MemoryExtensions.Reverse(Span<T>), which returns void (in-place).
  • .FirstOrDefault() fails to compile.

Fix 1 – Be Explicit with LINQ

Force the LINQ overload:

var endPart = request.SearchTerm
                        .Split(' ')
                        .AsEnumerable()
                        .Reverse()
                        .FirstOrDefault();

Fix 2 – Use LastOrDefault

Since I just needed the last token, this is cleaner:

var parts   = request.SearchTerm
                .Split(' ', StringSplitOptions.RemoveEmptyEntries);
var endPart = parts.LastOrDefault();

RedisValue Case

Another place that broke:

return JsonSerializer
        .Deserialize<RgsMapLineModel>(result);

In VS 2022, it compiled fine.
But in Insiders, result is a RedisValue (struct), not directly supported by JsonSerializer.

✅ Fix:

return JsonSerializer
        .Deserialize<RgsMapLineModel>(result
                                .ToString());

Lessons Learned

  • Implicit conversions to Span are picked up eagerly in the new compiler.
  • Be explicit with .AsEnumerable() or use Enumerable.Reverse() if you want LINQ.
  • For structs like RedisValue, always convert before passing to APIs like JsonSerializer.

Closing Thoughts

While this feels like a breaking change, it’s actually the compiler preferring high-performance APIs (Span<T>). That’s good for performance, but it means our code needs to be explicit about intent.

If you’re experimenting with VS Insiders, watch out for these tweaks!

A good read : Understanding C# overload-resolution

Last Note

Why does the compiler prefer Span<T>?

You might wonder why the new compiler prefers Span<T> overloads over string[] and LINQ.
The reason is performance: Span<T> lets you work with slices of arrays and strings without extra allocations.

Advantages of Span<T>

  • No extra memory allocations → avoids creating new arrays when slicing strings.
  • Safer than pointers → provides bounds checking while working with memory directly.
  • Faster iteration → direct access without LINQ’s deferred execution overhead.

Example

// Traditional LINQ
var words = text.Split(' ');
var last  = words.LastOrDefault();

// With Span<T>
ReadOnlySpan<char> span = text.AsSpan();
int idx = span.LastIndexOf(' ');
var lastWord = idx >= 0 ? span.Slice(idx + 1).ToString() : text;

The second approach avoids creating an intermediate string[] entirely.

👉 I’ll explore the power of Span<T> in depth (and when not to use it) in a follow-up blog.

Peace... 🍀

Tech Innovation Hub
Modern Software Architecture

Exploring cutting-edge technologies and architectural patterns that drive innovation in software development.

Projects

© 2025 Tech Innovation Hub. Built with Gatsby and modern web technologies.