C#で行番号を取得する

C#で行番号を取得する方法です。2種類あってそれぞれ以下の通りです。

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

static void Main(string[] args)
{
    GetLine_1();
    GetLine_2();
}

// C#4.0(VS2010)までの書き方
public static void GetLine_1()
{
    var stackFrame = new StackFrame(1, true);
    Console.WriteLine("行番号(1) = " + stackFrame.GetFileLineNumber());
}

// C#5.0以降(VS2012)からの書き方
public static void GetLine_2([CallerLineNumber]int line = 0)
{
    Console.WriteLine("行番号(2) = " + line);
}

// 実行結果

// Debugビルドを実行
> 行番号(1) = 15
> 行番号(2) = 16

// Releaseビルドを実行
> 行番号(1) = 16 // ?!
> 行番号(2) = 16

・・・古い方はReleaseビルドの行番号おかしいですね

また、メソッドに以下のような説明があるので、PDBを削除して実行すると

//
// 概要:
//     実行しているコードを格納しているファイルの行番号を取得します。
//     通常、この情報は実行可能ファイルのデバッグ シンボルから抽出されます。
//
// 戻り値:
//     ファイルの行番号。ファイルの行番号を特定できない場合は 0 (ゼロ)。
public virtual int GetFileLineNumber();

以下のように行番号が取れなくなります。

// DebugビルドでPDBを削除して実行
> 行番号(1) = 0 // ?!?!
> 行番号(2) = 16

なので古い方は使用しないほうが良いです。しかもstatic関数が最適化でインライン展開される場合があるようでソースと全然行数が合わないなんてことも発生たりします。そうなった場合GetMethod()でとれるMethodBaseも内容が変わってしまいます。悪いことだらけです。

逆に、新しい形式はコンパイル時にリテラルに置き換わるため問題起きません。

// ILSpy(逆コンパイルツール)でexeの中身を確認
GetLine_2(16);

呼び出し元の名前、パスも取得する

(C#5.0からは)行番号以外にもファイルパスや呼び出し元のメソッド名も取れます。

先ほどのGetLine_2を以下のように改造し実行するとそれぞれ対応した値が取得できます。

public static void GetLine_2([CallerLineNumber]int line = 0,
                             [CallerMemberName]string name ="",
                             [CallerFilePath]string path = "")
{
    Console.WriteLine("行番号(2) = " + line);
    Console.WriteLine("名前(2) = " + name);
    Console.WriteLine("パス(2) = " + path);
}

// 実行結果
> 行番号(2) = 12
> 名前 = Main
> パス = c:\work\ConsoleApp001\Program.cs // コンパイルした時のパス

まとめ

使うなら新しい方を使いましょう。