Streamfab.keepstreams.generic.hook-smeagol-ther... Apr 2026

if (disposing) // Hook gets notified first – it can release its own resources _hook.Dispose(_ctx);

// 1. Pre‑hook (may adjust the requested length) _hook.BeforeReadAsync(_ctx, destination, cancellationToken);

public void BeforeRead(IHookContext ctx, byte[] buffer, int offset, int count) /* … */ public void AfterRead(IHookContext ctx, byte[] buffer, int offset, int bytesRead) /* … */

return bytesRead;

var inner = provider.GetRequiredService<FileStream>(); var factory = provider.GetRequiredService<IHookFactory<MyCustomHook>>(); return new HookSmeagol<MyCustomHook>(inner, factory.Create(provider)); ); HookSmeagol can be stacked :

public override async ValueTask<int> ReadAsync( Memory<byte> destination, CancellationToken cancellationToken = default)

// Then the inner stream is disposed (unless the hook says otherwise) _inner.Dispose(); base.Dispose(disposing); StreamFab.KeepStreams.Generic.Hook-Smeagol-TheR...

return bytesRead;

The pattern mirrors Read ; the hook receives the buffer before the inner write and again after the write completes. 3.4 Seek , SetLength , Flush All these methods follow the same pre‑hook → inner operation → post‑hook flow. The async variants are implemented using ValueTask when possible to avoid allocations. 3.5 Disposal protected override void Dispose(bool disposing)

private readonly Stream _inner; private readonly THook _hook; private readonly IHookContext _ctx; // … if (disposing) // Hook gets notified first –

Typical overhead for a (i.e., a hook that just forwards everything) is ≈ 30–50 ns per call on modern .NET runtimes – negligible for most I/O‑bound workloads. Real‑world hooks (logging, encryption, compression) dominate the cost, not the wrapper. 7. Debugging & diagnostics HookSmeagol ships with a built‑in diagnostic source ( System.Diagnostics.DiagnosticListener ) named "StreamFab.KeepStreams.HookSmeagol" . It emits the following events:

public void BeforeWrite(IHookContext ctx, byte[] buffer, int offset, int count) /* … */ public void AfterWrite(IHookContext ctx, byte[] buffer, int offset, int count) /* … */