C# Story

Chapter #15 – Interesting

async/await, reflection, attributes, Span<T>, unsafe, source generators

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