if (disposing) // Hook gets notified first – it can release its own resources _hook.Dispose(_ctx);
var listener = new DiagnosticListener("StreamFab.KeepStreams.HookSmeagol"); listener.Subscribe(new MyObserver()); These events are invaluable when you need to without modifying the hook code itself. 8. Common pitfalls & how to avoid them | Pitfall | Symptom | Fix | |---------|---------|-----| | Double‑dispose | ObjectDisposedException on later reads/writes. | Ensure the hook does not call Dispose on the inner stream unless it owns it. The wrapper already disposes the inner stream once. | | Blocking async hooks | Thread‑pool starvation, deadlocks. | Never use .Result / .Wait() inside async hook methods; always await . | | Changing CanSeek | Consumer thinks the stream is seekable but it isn’t. | Propagate CanSeek from the inner stream unchanged; if you need to add seeking (e.g., buffering), expose a new wrapper type rather than HookSmeagol . | | Unbounded memory growth | Hook buffers grow without limit (e.g., a logging hook that stores every payload). | Use bounded buffers or stream the data to a file/DB as it arrives. | | Incorrect async signature | ValueTask returned but not awaited → lost exceptions. | Always await the returned ValueTask inside the wrapper (the library already does this). | 9. Sample end‑to‑end usage Below is a short, self‑contained console demo that composes three hooks:
| Responsibility | Why it matters | |----------------|----------------| | inbound/outbound data flowing through any System.IO.Stream ‑derived object without breaking the original contract. | Enables logging, diagnostics, transformation, or throttling of data pipelines (e.g., network sockets, file streams, compression streams). | | Preserve the original stream’s semantics (async/sync, seeking, length, timeouts). | Guarantees drop‑in replacement – callers do not need to change their code. | | Compose multiple hooks (e.g., logging + encryption + compression) in a deterministic order. | Keeps the pipeline modular and testable. | | Dispose safely – the hook forwards Dispose / DisposeAsync while also releasing its own resources (buffers, diagnostic listeners). | Prevents resource leaks in long‑running services. | StreamFab.KeepStreams.Generic.Hook-Smeagol-TheR...
public void Dispose(IHookContext ctx) /* free any unmanaged resources */
// 3. Post‑hook (e.g., logging, decryption, metrics) await _hook.AfterReadAsync(_ctx, destination.Slice(0, bytesRead), cancellationToken) .ConfigureAwait(false); if (disposing) // Hook gets notified first –
private readonly Stream _inner; private readonly THook _hook; private readonly IHookContext _ctx; // …
using System; using System.IO; using System.IO.Compression; using System.Threading.Tasks; using StreamFab.KeepStreams.Generic; | Ensure the hook does not call Dispose
// 2. The inner stream performs the real read int bytesRead = _inner.Read(buffer, offset, count);