HomeNikola Knezevic

In this article

Banner

MethodTimer by Fody in .NET

27 Aug 2026
5 min

Sponsor Newsletter

When optimizing an application, we often need to know how long a specific method takes to run.

The usual approach is to wrap the method body with a Stopwatch, start it, stop it and write the elapsed time somewhere.

That works, but it quickly becomes noisy. Timing logic gets mixed with business logic and the same boilerplate spreads across many methods.

That's where MethodTimer.Fody comes in. It injects timing code for you at compile time, so your source stays clean.

MethodTimer by Fody

MethodTimer is a Fody add-in that weaves basic method timing into your assembly during build.

You mark a method (or even a whole class) with the [Time] attribute. At compile time Fody rewrites those methods to measure duration and log the result.

The attribute itself is removed from the final assembly, so there is no runtime reflection cost for discovering what to time.

This is great for development and diagnostics. If you need scientific micro-benchmarks instead, check out my blog post on Benchmarking with BenchmarkDotNet.

Getting Started

Install the NuGet package. Fody recommends installing Fody explicitly as well, since NuGet can resolve an older dependency otherwise:

shell
Install-Package Fody
Install-Package MethodTimer.Fody

Next, make sure MethodTimer is listed in your FodyWeavers.xml file:

xml
<Weavers>
  <MethodTimer />
</Weavers>

That's all the setup you need. The package also adds a TimeAttribute source file to your project for you to use.

Timing Methods

Applying MethodTimer is as simple as decorating a method with [Time]:

csharp
using MethodTimer;

public class WeatherService
{
    [Time]
    public async Task<List<WeatherForecast>> GetWeatherForecastWithTiming(int days)
    {
        await Task.Delay(Random.Shared.Next(10, 50));

        return Enumerable.Range(1, days).Select(index => new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-25, 50),
            Summary = GetRandomSummary()
        }).ToList();
    }
}

Your method body stays focused on the work it should do. No stopwatch. No logging calls mixed into the business logic.

You can also put [Time] on a class, constructor, module or assembly if you want broader coverage.

What Gets Compiled

Without a custom interceptor, MethodTimer rewrites the method roughly like this:

csharp
public void MyMethod()
{
    var start = Stopwatch.GetTimestamp();
    try
    {
        // Your original method body
        Console.WriteLine("Hello");
    }
    finally
    {
        var end = Stopwatch.GetTimestamp();
        var elapsed = end - start;
        var elapsedTimeSpan = new TimeSpan(...);
        Trace.WriteLine("MyClass.MyMethod " + elapsedTimeSpan.TotalMilliseconds + "ms");
    }
}

Notice a few important details.

  • Allocation-friendly timing - It uses Stopwatch.GetTimestamp instead of allocating a Stopwatch instance.
  • try/finally - Duration is still reported if the method throws.
  • Trace.WriteLine - By default the timing message goes to the trace listeners, which is handy in development.

After a rebuild, call the timed method and check your Debug output. You should see something like WeatherService.GetWeatherForecastWithTiming 34ms.

Custom Logging

Writing to Trace is fine for quick checks, but most apps want timings in Serilog, Application Insights or another logger.

To take control, define a static MethodTimeLogger class with a Log method. MethodTimer will call it instead of Trace.

There are two supported signatures. When both exist, the TimeSpan overload is preferred:

csharp
using System.Reflection;

public static class MethodTimeLogger
{
    public static void Log(MethodBase methodBase, TimeSpan elapsed, string message)
    {
        Console.WriteLine($"{methodBase.DeclaringType?.Name}.{methodBase.Name} took {elapsed.TotalMilliseconds}ms {message}");
    }
}

Or use milliseconds as a long:

csharp
public static class MethodTimeLogger
{
    public static void Log(MethodBase methodBase, long milliseconds, string message)
    {
        // Send to your preferred logging pipeline
    }
}

From there you can enrich logs however you like. For request-scoped tracing across services, see my post on Enriching Logs with Correlation ID.

Including Parameter Values

Sometimes the duration alone is not enough. You also want to know which input made the method slow.

MethodTimer supports a format string on the attribute, so parameter values can be included in the logged message:

csharp
[Time("Days: {days}")]
public async Task<List<WeatherForecast>> GetWeatherForecastWithTiming(int days)
{
    // ...
}

Allowed placeholders are parameter names and {this} for the instance's ToString() value. Sub-properties are not supported.

NOTE: Parameter formatting requires a MethodTimeLogger.Log method that accepts the message parameter. Without it, the weaver will raise a build error.

Conclusion

MethodTimer by Fody is a lightweight way to measure method duration without cluttering your code.

Mark what you care about with [Time], rebuild and inspect the timings. When you need more control, add a MethodTimeLogger and route the results into your existing logging stack.

Keep it for diagnostics and local investigation. For rigorous performance comparisons, reach for BenchmarkDotNet instead.

If you want to check out examples I created, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.