CSharpStory_Interesting.html
copyright © James Fawcett
Revised: 04/26/2026
15.0 Prologue
This final chapter collects several features that are particularly useful, distinctive,
or worth understanding deeply: the async/await model in detail, reflection, attributes,
high-performance Span<T>, unsafe code, and the source-generator pipeline.
15.1 Async / Await in Depth
async / await transforms a method into a state machine that
suspends at each await of an incomplete Task and resumes when
that task completes — without blocking a thread.
async Task<int> SumPageLengthsAsync(IEnumerable<string> urls) {
using var http = new HttpClient();
var tasks = urls.Select(u => http.GetStringAsync(u));
string[] pages = await Task.WhenAll(tasks); // concurrent fetch
return pages.Sum(p => p.Length);
}
Key rules:
- Prefer Task return types; avoid async void except for event handlers
- Use ConfigureAwait(false) in library code to avoid deadlocks in UI/ASP.NET contexts
- Use CancellationToken for cooperative cancellation:
await DoWorkAsync(token)
- ValueTask<T> avoids heap allocation when the result is often
synchronously available
- IAsyncEnumerable<T> and await foreach enable async
streaming (C# 8+)
// async streaming
async IAsyncEnumerable<int> CountAsync(int n,
[EnumeratorCancellation] CancellationToken ct = default) {
for (int i = 0; i < n; i++) {
await Task.Delay(100, ct);
yield return i;
}
}
await foreach (int v in CountAsync(10))
Console.WriteLine(v);
15.2 Span<T> and Memory<T>
Span<T> is a stack-only, ref struct that represents a contiguous
region of memory (an array slice, stack-allocated block, or native buffer) without
copying. It enables high-performance parsing and data processing:
void ProcessBuffer(ReadOnlySpan<byte> data) {
while (!data.IsEmpty) {
int len = data[0];
ReadOnlySpan<byte> chunk = data.Slice(1, len);
Handle(chunk);
data = data.Slice(1 + len);
}
}
// zero-copy string slicing
ReadOnlySpan<char> csv = "Alice,30,Engineer".AsSpan();
int comma = csv.IndexOf(',');
ReadOnlySpan<char> name = csv[..comma]; // "Alice" — no allocation
Memory<T> is the heap-safe counterpart — it can be stored in fields
and used across async boundaries. It wraps an array segment and provides a
Span on demand via .Span.
ArrayPool<T>.Shared.Rent(n) / .Return(buf) avoids
allocating large temporary arrays on the heap.
15.3 Reflection
Reflection allows code to inspect and invoke types, methods, and properties at runtime
through the System.Reflection namespace:
Type t = typeof(List<int>);
Console.WriteLine(t.FullName); // System.Collections.Generic.List`1
foreach (var m in t.GetMethods(BindingFlags.Public | BindingFlags.Instance))
Console.WriteLine(m.Name);
// late-bound invocation
object? obj = Activator.CreateInstance(t);
MethodInfo? add = t.GetMethod("Add");
add?.Invoke(obj, new object[] { 42 });
Reflection is used by serializers, dependency injection frameworks, ORMs, and testing
tools. It carries a runtime cost; for hot paths prefer source generators or compiled
expressions.
15.4 Attributes
Attributes attach declarative metadata to types, methods, parameters,
or assemblies. They are read at runtime via reflection and at compile time via source
generators or Roslyn analyzers:
[Obsolete("Use NewMethod instead", error: false)]
public void OldMethod() { }
[Serializable]
public class Config {
[JsonPropertyName("max_retries")]
public int MaxRetries { get; set; }
}
// custom attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class AuditAttribute : Attribute {
public string Reason { get; }
public AuditAttribute(string reason) => Reason = reason;
}
[Audit("PCI compliance")]
public class PaymentProcessor { /* ... */ }
Important built-in attributes:
[Obsolete], [Serializable],
[Flags] (on enums), [ThreadStatic],
[MethodImpl(MethodImplOptions.AggressiveInlining)],
[CallerMemberName] / [CallerFilePath] / [CallerLineNumber].
15.5 Unsafe Code and Pointers
C# lets you drop out of managed memory safety within unsafe blocks.
Unsafe code is required for pointer arithmetic, pinning objects for native interop,
and calling certain P/Invoke signatures.
unsafe void Xor(byte* buf, int len, byte key) {
for (int i = 0; i < len; i++)
buf[i] ^= key;
}
// pin a managed array so GC won't move it
byte[] data = { 1, 2, 3, 4 };
fixed (byte* p = data) {
Xor(p, data.Length, 0xFF);
}
Enable unsafe code with <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
in the project file. Use Span<T> and Marshal as safer
alternatives before resorting to raw pointers.
15.6 Source Generators
Source generators (Roslyn incremental generators, C# 9+) run as part
of compilation and emit additional C# source files. They replace reflection-based
patterns with zero-cost generated code:
- System.Text.Json source generation — AOT-safe JSON serialization
- LoggerMessage.Define — high-performance structured logging
- Regex source generator ([GeneratedRegex]) — compile-time regex compilation
- DI source generation — compile-time service wiring
// Regex source generator — no runtime compilation overhead
[GeneratedRegex(@"\d{4}-\d{2}-\d{2}")]
private static partial Regex DatePattern();
bool ok = DatePattern().IsMatch("2026-04-26"); // true
15.7 Advanced Pattern Matching
C# pattern matching has grown substantially since its introduction in C# 7. Patterns
can be combined with logical operators and used in if, switch,
and is expressions:
object obj = new Point(3, -1);
// type, deconstruct, and property patterns combined
string msg = obj switch {
Point { X: 0, Y: 0 } => "origin",
Point { X: var x, Y: var y } when x == y => "on diagonal",
Point { Y: < 0 } => "below x-axis",
Point (var x, _) when x > 0 => "right half-plane",
null => "null",
_ => "other"
};
List patterns (C# 11) match array or list shapes:
if (data is [1, 2, .., > 0])
15.8 Epilogue
This chapter sampled some of the more advanced and interesting corners of C#: the async
state-machine model, zero-copy Span<T>, runtime reflection, declarative attributes,
unsafe pointer code, source generators, and advanced pattern matching. C# continues to
evolve; each release brings new features that make the language safer, faster, and more
expressive.
15.9 References
Async programming — Microsoft docs
Memory<T> & Span<T> — Microsoft docs
Reflection and attributes
Source generators overview
Pattern matching — Microsoft docs