Lightweight method timing via DispatchProxy. Mark methods with [Timed] and get timing output in DEBUG builds. Pluggable logger, threshold filtering, and ValueTask support.
$ dotnet add package Dot.TickLightweight method timing for .NET using DispatchProxy. Add [Timed] to your implementation methods and get simple, precise timing logs in DEBUG builds with near‑zero overhead in RELEASE.
[Timed] is Conditional("DEBUG") and is not emitted in Release builds.IElapsedLogger, GlobalThreshold).DotTickProxy.Create<T>(impl).Install from NuGet:
PM> Install-Package Dot.Tick
# or
> dotnet add package Dot.Tick
public interface ICalculator
{
int Add(int a, int b);
Task<int> DelayAddAsync(int a, int b, int delayMs);
}
public class Calculator : ICalculator
{
[Timed]
public int Add(int a, int b) => a + b;
[Timed]
public async Task<int> DelayAddAsync(int a, int b, int delayMs)
{
await Task.Delay(delayMs);
return a + b;
}
}
var calc = DotTickProxy.Create<ICalculator>(new Calculator());
var sum = await calc.DelayAddAsync(1, 2, 10);
In DEBUG builds you will see console output like:
[Dot.Tick] Namespace.Calculator.DelayAddAsync took 10.123 ms
Failures include a suffix:
[Dot.Tick] Namespace.Calculator.FailSync took 0.642 ms (failed)
Register a proxied service in your service container:
builder.Services.AddSingleton<ICalculator>(sp => DotTickProxy.Create<ICalculator>(new Calculator()));
Then inject and use ICalculator as usual in your endpoints/controllers. In DEBUG, timed methods will log to the console output.
You can configure Dot.Tick globally. Defaults: enabled in DEBUG, console logger, no threshold.
DotTick.Configure(o =>
{
o.GlobalThreshold = TimeSpan.FromMilliseconds(5); // only log when >= 5ms
o.Logger = new MyStructuredLogger(); // plug your own logger
o.Enabled = true; // enable/disable at runtime (DEBUG only)
});
Implement your own logger by implementing IElapsedLogger:
public sealed class MyStructuredLogger : IElapsedLogger
{
public void Log(string typeName, string methodName, TimeSpan elapsed, bool failed)
{
// Route to Serilog / ILogger / Application Insights, etc.
Console.WriteLine($"[Dot.Tick] {typeName}.{methodName} took {elapsed.TotalMilliseconds:F3} ms {(failed ? "(failed)" : "")}");
}
}ValueTask and ValueTask<T> are awaited and measured correctly:
public interface IAsyncCalc
{
ValueTask DelayNoResultAsync(int ms);
ValueTask<int> DelayAddValueTaskAsync(int a, int b, int ms);
}
public class AsyncCalc : IAsyncCalc
{
[Timed]
public async ValueTask DelayNoResultAsync(int ms) => await Task.Delay(ms);
[Timed]
public async ValueTask<int> DelayAddValueTaskAsync(int a, int b, int ms)
{
await Task.Delay(ms);
return a + b;
}
}
var calc = DotTickProxy.Create<IAsyncCalc>(new AsyncCalc());
await calc.DelayNoResultAsync(5);
var sum = await calc.DelayAddValueTaskAsync(3, 7, 5);DispatchProxy to create a proxy for your interface.[Timed], Dot.Tick starts a stopwatch and logs on completion or exception.[Timed] is compiled out entirely, so there is no attribute to detect; the proxy simply calls through with negligible overhead.[Timed] is effective only in DEBUG builds. You can still disable logging in DEBUG at runtime via DotTick.Configure.[Timed] on the concrete implementation method, not on the interface.DotTickProxy.Create<T>(); calling the raw implementation bypasses timing.DispatchProxy limitation).GlobalThreshold filters out fast calls; set to TimeSpan.Zero to log everything.Demo/Dot.Tick.Demo.ConsoleDemo/Dot.Tick.Demo.WebApiDot.Tick.Tests (show DEBUG/RELEASE differences in logging)[Timed] and that the method is part of the proxied interface.GlobalThreshold to a non‑zero value.MIT