Chapter 13 Improving Efficiency With More Pass By Reference

3 min read
Rapid overview

Chapter 13 — Improving efficiency with more pass by reference

Purpose of this chapter: understand modern by-ref features (ref locals/returns, in parameters, readonly structs, ref-like structs like Span<T>) so you can reason about performance and correctness without introducing unsafe bugs.

This is performance-focused C#: use only when it measurably matters and you can keep invariants clear.


13.1 Recap: what ref means

Passing/returning by reference means you’re working with an alias to storage, not a copy.

Core risks:

  • Aliasing can make reasoning harder (“who else can mutate this?”).
  • Lifetime rules become critical (you must not return refs to dead storage).

13.2 Ref locals and ref returns

13.2.1 Ref locals

Ref locals can refer directly to an element/storage location:

  • Used to avoid copying and to update data in-place.

13.2.2 Ref returns

Methods can return by-ref to let callers mutate data in-place (or avoid copying large structs).

Senior guidance:

  • Expose ref returns only when the underlying storage lifetime is guaranteed and the API is internal or carefully designed.

13.2.3 Conditional operator with ref values (C# 7.2)

The conditional operator can work in ref contexts:

  • Useful for selecting one of two storage locations.
  • Very easy to misuse; keep it simple and well-tested.

13.2.4 ref readonly (C# 7.2)

ref readonly returns a reference that cannot be used to mutate through that reference:

  • Helps avoid copying while preserving “read-only through this alias” intent.

13.3 in parameters (C# 7.2)

in parameters pass by reference but are intended as read-only at the call site.

Why it exists:

  • Avoid copying large structs by passing by ref.

13.3.1 Compatibility considerations

in can affect overload resolution and API shape. Be cautious in public APIs.

13.3.2 Surprising mutability: external changes

in prevents mutation through the parameter, but:

So in is not “deep immutability”; it’s “read-only through this name”.

  • The underlying storage might still be mutable via other aliases.

13.3.3 Overloading with in

Overloads can become ambiguous or surprising. Keep APIs simple.

13.3.4 Guidance for in

Use in when:

  • The argument is a large struct and copying is measurable.
  • The callee should not mutate through the parameter.

Avoid in when:

  • It adds complexity without evidence.
  • The type is small (copying is cheap).

13.4 Readonly structs (C# 7.2)

Readonly structs help the compiler avoid defensive copies and communicate intent.

13.4.1 Implicit copying with read-only variables

If a struct isn’t readonly, calling instance members on a readonly reference can force defensive copies (to preserve logical immutability). This can surprise you in perf-sensitive code.

13.4.2 readonly modifier for structs

Marking a struct readonly:

  • Signals that instance members won’t mutate state.
  • Enables optimizations and avoids some defensive copies.

13.4.3 XML serialization caveat

Some serializers assume settable properties/fields; readonly structs can be awkward for frameworks expecting mutable state.


13.5 Extension methods with ref/in (C# 7.2)

You can write extension methods that avoid copies by taking in or ref.

Guideline:

  • Use sparingly; don’t create surprising APIs where calling code mutates via what looks like a normal instance call.

13.6 Ref-like structs (C# 7.2)

Ref-like structs (e.g., Span<T>) represent stack-only / by-ref-only values with strict lifetime rules.

13.6.1 Rules (why they exist)

Typical restrictions:

  • Can’t be boxed.
  • Can’t be stored on the heap (fields of normal classes, captured by lambdas/async, etc.).

These prevent dangling references to stack memory.

13.6.2 Span<T> and stackalloc

Span<T> enables safe “window over memory” programming:

  • Used for high-performance parsing/formatting and zero-copy slicing.

13.6.3 IL representation

You don’t need IL details for interviews, but you should be able to say:

  • The runtime/compiler enforce rules so references don’t outlive the underlying storage.