LoginSignup
5
4

More than 5 years have passed since last update.

式木を使った簡単なPrintデバッグ出力

Last updated at Posted at 2018-01-13

概要

アプリケーションを実行中に変数の値を確認したい際には
Console.WriteLine()Debug.WriteLine()が最も使われると思います。
一般的にはPrintデバッグと言われていますね(C言語のprintから?)。

私はこの際に下記コードのように変数名 = 変数値の文字列に整形しています。

Console.WriteLine($"{nameof(pTaro)} = {pTaro}");

これを毎回書くのがダルくなってきたので、式木を使って下記コードのように簡単にする方法を紹介します。

MyReport.ConsoleOut(() => pTaro);

変更前

今回はConsoleアプリケーションを使用しますので、Console.WriteLine()を使用します。
WPF等の場合や、VisualStudioの出力ウインドウを使用したい場合はDebug.WriteLine()を使用します。

まず出力されるクラスとしてPersonクラスを作っておきます。
この時ToString()をオーバーライドしておくと、デバッグが捗ります。

class Person
{
    public string Name { get; set; }
    public override string ToString() => Name;
}

次にPersonクラスを使用してこんなサンプルプログラムを作ります。

class Program
{
    static void Main(string[] args)
    {
        var pTaro = new Person { Name = "太郎" };
        var pHithoshi = new Person { Name = "均" };
        Console.WriteLine($"{nameof(pTaro)} = {pTaro}");
        Console.WriteLine($"{nameof(pHithoshi)} = {pHithoshi}");

        Console.WriteLine("===太郎は進化しました===");
        pTaro.Name = "NEOTARO";

        Console.WriteLine($"{nameof(pTaro)} = {pTaro}");
        Console.WriteLine($"{nameof(pHithoshi)} = {pHithoshi}");

        Console.ReadLine();
    }
}

このプログラムの出力結果はこんな感じです。

Console出力結果
pTaro = 太郎
pHithoshi = 均
===太郎は進化しました===
pTaro = NEOTARO
pHithoshi = 均

めんどうなところ

Console.WriteLine($"{nameof(pTaro)} = {pTaro}");
とか毎回書くのがダルいです。特にnameofとかが。
そこでこの部分だけメソッドに抽出してpTaroだけ引数として渡そうとしても、今度はnameofの結果が意図通りになりません。

失敗メソッド
static void ReportFail(Person person)
    => Console.WriteLine($"{nameof(person)} = {person}");
Console出力結果
person = 太郎

解決策

そこで式木を使用したメソッドでこの問題を解決します。

static class MyReport
{
    /// <summary>
    /// ConsoleにReport出力
    /// </summary>
    public static void ConsoleOut(Expression<Func<object>> exp)
        => Console.WriteLine(CreateReportText(exp));

    /// <summary>
    /// DebugにReport出力
    /// </summary>
    public static void DebugOut(Expression<Func<object>> exp)
        => Debug.WriteLine(CreateReportText(exp));

    /// <summary>
    /// 式木を使用してReport用文字列に変換
    /// </summary>
    /// <param name="exp">Report対象の式木 ex. ()=>MyAge</param>
    /// <returns>"MyAge = 17"</returns>
    public static string CreateReportText(Expression<Func<object>> exp)
    {
        var memName = (exp.Body as MemberExpression)?.Member.Name;
        var result = exp.Compile().Invoke();
        return $"{memName} = {result}";
    }
}

出力したい変数やプロパティを出力するデリゲートを式木で受け取ります。
一番簡単なのはラムダ式で()=>変数と書くことです。

CreateReportText内部では受け取った式木を分解して変数名を取り出しています。
その後式木をコンパイルしてデリゲートに変換し、即実行して変数の値を取得します。
そして両者を文字列として整形しています。

変更後

使用するプログラムを変更します。

class Program
{
    static void Main(string[] args)
    {
        var pTaro = new Person { Name = "太郎" };
        var pHithoshi = new Person { Name = "均" };

        MyReport.ConsoleOut(() => pTaro);
        MyReport.ConsoleOut(() => pHithoshi);
        Console.WriteLine("===太郎は進化しました===");
        pTaro.Name = "NEOTARO";
        MyReport.ConsoleOut(() => pTaro);
        MyReport.ConsoleOut(() => pHithoshi);

        Console.ReadLine();
    }
}

見た目の長さはあまり変わりませんが、インテリセンスを使用した書きやすさは段違いです。
出力結果は同じです。

問題点

Reportのたびに内部で式木からデリゲートを生成しているので重いです。
パフォーマンスがネックになるのであれば、内部でデリゲートをキャッシュする必要があります。

参考

[C#][ラムダ式][式木] Expression を使ってラムダ式のメンバー名を取得する (プログラミング C# - 翔ソフトウェア (Sho's))

環境

VisualStudio2017
.NET Framework 4.7

5
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4