C# メソッドの実行時間・処理時間を測定する

## 概要
C# であるメソッドの実行時間・処理時間を測定する処理をよく書くので、備忘録としてメモを残しておきます。
```csharp:簡易コード
using System.Diagnostics;
public static (TResult result, TimeSpan elapsed) MeasureExecutionTime<TResult>(Func<TResult> method)
{
    var stopwatch = Stopwatch.StartNew();
    TResult result = method();
    stopwatch.Stop();
    return (result, stopwatch.Elapsed);
}
```
試行錯誤してこの形になりました。

## コード全文

```csharp:MethodTimer.cs
// These codes are licensed under CC0. （権利放棄、自由利用可）
//https://creativecommons.org/publicdomain/zero/1.0/deed.ja
using System.Diagnostics;

namespace MethodTimer;

/// <summary>
/// メソッドの実行時間を測定するためのユーティリティクラス
/// </summary>
public static class MethodTimer
{
    /// <summary>
    /// 戻り値のあるメソッドの実行時間を測定
    /// </summary>
    /// <typeparam name="TResult">メソッドの戻り値の型</typeparam>
    /// <param name="method">実行時間を測定する対象のメソッド</param>
    /// <returns>
    /// <list type="bullet">
    /// <item><description>result: メソッドの実行結果</description></item>
    /// <item><description>elapsed: 実行にかかった時間</description></item>
    /// </list>
    /// </returns>
    /// <example>
    /// 戻り値のあるメソッドの実行時間を測定する例
    /// <code>
    /// var (result, elapsed) = MethodTimer.MeasureExecutionTime(() => SomeMethod());
    /// Console.WriteLine($"Result: {result}, Elapsed Time: {elapsed.TotalMilliseconds} ms");
    /// </code>
    /// </example>
    public static (TResult result, TimeSpan elapsed) MeasureExecutionTime<TResult>(Func<TResult> method)
    {
        var stopwatch = Stopwatch.StartNew();
        TResult result = method();
        stopwatch.Stop();
        return (result, stopwatch.Elapsed);
    }

    /// <summary>
    /// 戻り値のないメソッドの実行時間を測定
    /// </summary>
    /// <param name="method">実行時間を測定する対象のメソッド</param>
    /// <returns>実行にかかった時間</returns>
    /// <example>
    /// 戻り値のないメソッドの実行時間を測定する例
    /// <code>
    /// var elapsed = MethodTimer.MeasureExecutionTime(() => SomeVoidMethod());
    /// Console.WriteLine($"Elapsed Time: {elapsed.TotalMilliseconds} ms");
    /// </code>
    /// </example>
    public static TimeSpan MeasureExecutionTime(Action method)
    {
        var stopwatch = Stopwatch.StartNew();
        method();
        stopwatch.Stop();
        return stopwatch.Elapsed;
    }

    /// <summary>
    /// 戻り値のあるメソッドの実行時間を測定し、結果をコンソールに出力
    /// </summary>
    /// <typeparam name="TResult">メソッドの戻り値の型</typeparam>
    /// <param name="method">実行時間を測定する対象のメソッド</param>
    /// <returns>メソッドの実行結果</returns>
    /// <example>
    /// 戻り値のあるメソッドの実行時間を測定してログに出力する例
    /// <code>
    /// var result = MethodTimer.LogExecutionTime(() => SomeMethod());
    /// </code>
    /// </example>
    public static TResult LogExecutionTime<TResult>(Func<TResult> method)
    {
        var stopwatch = Stopwatch.StartNew();
        TResult result = method();
        stopwatch.Stop();
        Console.WriteLine($"Result: {result}, Elapsed Time: {stopwatch.Elapsed.TotalMilliseconds} ms");
        return result;
    }

    /// <summary>
    /// 戻り値のないメソッドの実行時間を測定し、結果をコンソールに出力
    /// </summary>
    /// <param name="method">実行時間を測定する対象のメソッド</param>
    /// <example>
    /// 戻り値のないメソッドの実行時間を測定してログに出力する例
    /// <code>
    /// MethodTimer.LogExecutionTime(() => SomeVoidMethod());
    /// </code>
    /// </example>
    public static void LogExecutionTime(Action method)
    {
        var stopwatch = Stopwatch.StartNew();
        method();
        stopwatch.Stop();
        Console.WriteLine($"Elapsed Time: {stopwatch.Elapsed.TotalMilliseconds} ms");
    }
}
```

## 使用例
```csharp:Program.cs
using static MethodTimer.MethodTimer;

class Program
{
    private static void Main()
    {
        //使用例
        var fuga = new FugaClass();
        var piyo = new PiyoClass { Value = "Hello" };

        // 戻り値あり、引数あり
        var (result0, elapsed0) = MeasureExecutionTime(() => fuga.FugaMethod(piyo));
        Console.WriteLine($"Result: {result0.Value}, Elapsed Time (result, arg): {elapsed0.TotalMilliseconds} ms");
        Console.WriteLine();

        // 戻り値なし、引数あり
        var elapsed1 = MeasureExecutionTime(() => fuga.FugaMethodVoid(piyo));
        Console.WriteLine($"Elapsed Time (void, arg): {elapsed1.TotalMilliseconds} ms");
        Console.WriteLine();

        // 戻り値あり、引数なし
        var (result2, elapsed2) = MeasureExecutionTime(fuga.FugaMethodString);
        Console.WriteLine($"Result: {result2}, Elapsed Time (result, no arg): {elapsed2.TotalMilliseconds} ms");
        Console.WriteLine();

        // 戻り値なし、引数なし
        var elapsed3 = MeasureExecutionTime(fuga.FugaMethodVoid);
        Console.WriteLine($"Elapsed Time (void, no arg): {elapsed3.TotalMilliseconds} ms");
        Console.WriteLine();

        //コンソールに実行時間を出力
        //戻り値あり、引数あり
        HogeClass? result3 = LogExecutionTime(() => fuga.FugaMethod(piyo));
        Console.WriteLine($"Result: {result3.Value}");

        //戻り値なし、引数あり
        LogExecutionTime(() => fuga.FugaMethodVoid(piyo));
        Console.WriteLine();

        //戻り値あり、引数なし
        string? result5 = LogExecutionTime(fuga.FugaMethodString);
        Console.WriteLine($"Result: {result5}");
        Console.WriteLine();
        //戻り値なし、引数なし
        LogExecutionTime(fuga.FugaMethodVoid);
        Console.WriteLine();
    }
}

public class HogeClass
{
    public int Value { get; set; }
}

public class FugaClass
{
    public HogeClass FugaMethod(PiyoClass arg)
    {
        Thread.Sleep(400); // ダミー処理
        Console.WriteLine($"FugaMethod executed with arg: {arg.Value}");
        return new HogeClass() { Value = 0xff };
    }

    public void FugaMethodVoid(PiyoClass arg)
    {
        Thread.Sleep(300); // ダミー処理
        Console.WriteLine($"FugaMethodVoid executed with arg: {arg.Value}");
    }

    public string FugaMethodString()
    {
        Thread.Sleep(200); // ダミー処理
        return "Hello, World! by FugaMethodString";
    }

    public void FugaMethodVoid()
    {
        Thread.Sleep(100); // ダミー処理
        Console.WriteLine("Void method with no arguments executed");
    }
}

public class PiyoClass
{
    public string Value { get; set; }
}
```

### 実行結果
```:実行結果
FugaMethod executed with arg: Hello
Result: 255, Elapsed Time (result, arg): 428.6967 ms

FugaMethodVoid executed with arg: Hello
Elapsed Time (void, arg): 316.5747 ms

Result: Hello, World! by FugaMethodString, Elapsed Time (result, no arg): 212.1155 ms

Void method with no arguments executed
Elapsed Time (void, no arg): 105.722 ms

FugaMethod executed with arg: Hello
Result: HogeClass, Elapsed Time: 401.6136 ms
Result: 255
FugaMethodVoid executed with arg: Hello
Elapsed Time: 307.6023 ms

Result: Hello, World! by FugaMethodString, Elapsed Time: 214.6779 ms
Result: Hello, World! by FugaMethodString

Void method with no arguments executed
Elapsed Time: 107.0708 ms

```

### 使用環境
Visual Studio 2022
.NET 9.0 / C# バージョン 13.0


## 参考資料
Stopwatch クラス (System.Diagnostics) | Microsoft Learn 

https://learn.microsoft.com/ja-jp/dotnet/api/system.diagnostics.stopwatch

ラムダ式 - ラムダ式と匿名関数 - C# reference | Microsoft Learn 

https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/lambda-expressions 

Func<TResult> Delegate (System) | Microsoft Learn 

https://learn.microsoft.com/ja-jp/dotnet/api/system.func-1

デリゲート - C# によるプログラミング入門 | ++C++; // 未確認飛行 C 

https://ufcpp.net/study/csharp/sp_delegate.html