【C#】呼び出し元のメソッド名・ファイルパス・行番号を取得する

目次

概要

メソッドが「どこから」「誰によって」呼び出されたかをプログラム内で特定する実装です。 .NET標準の Caller Information Attributes(呼び出し元情報属性)を使用することで、ログ出力やデバッグ時に、呼び出し元のソースコード情報を引数として自動的に受け取ることができます。

仕様(入出力)

  • 入力
    • メソッドのオプション引数として定義された文字列や整数。
    • 呼び出し側は引数を明示的に指定する必要はありません(コンパイラが自動的に値を埋め込みます)。
  • 出力
    • CallerFilePath: 呼び出し元のソースファイルのフルパス(string
    • CallerLineNumber: 呼び出し元の行番号(int
    • CallerMemberName: 呼び出し元のメソッド名やプロパティ名(string
  • 前提
    • System.Runtime.CompilerServices 名前空間が必要です。
    • 対象の引数にはデフォルト値(= ""= 0)の設定が必須です。

基本の使い方

以下のように、メソッドの引数に属性を付与するだけで機能します。呼び出し側は通常通りメソッドを呼ぶだけです。

// 定義側
public void Log(string message, 
    [CallerMemberName] string member = "", 
    [CallerLineNumber] int line = 0)
{
    Console.WriteLine($"{member}({line}): {message}");
}

// 呼び出し側
Log("Error occurred"); 
// 出力例: Main(15): Error occurred

コード全文

独自のロガークラスを定義し、呼び出し元の情報を付与してコンソールに出力するコンソールアプリケーションです。

using System;
using System.Runtime.CompilerServices;
using System.IO;

class Program
{
    static void Main()
    {
        // 1. 通常のメソッド呼び出し
        // 引数にファイルパスなどを指定しなくても、自動的にコンパイラが挿入する
        MyLogger.Write("処理を開始します。");

        // 2. 別のメソッドからの呼び出し
        ProcessData();
    }

    static void ProcessData()
    {
        MyLogger.Write("データを処理中...");
        
        try
        {
            // エラー時のログ出力例
            throw new InvalidOperationException("DB接続エラー");
        }
        catch (Exception ex)
        {
            MyLogger.Write($"例外発生: {ex.Message}");
        }
    }
}

// 汎用ロガークラス
public static class MyLogger
{
    // 属性を付与した引数には、必ずデフォルト値が必要
    public static void Write(string message,
        [CallerFilePath] string file = "",
        [CallerLineNumber] int line = 0,
        [CallerMemberName] string member = "")
    {
        // フルパスだと長すぎるため、ファイル名のみ抽出する例
        string fileName = Path.GetFileName(file);
        
        string logText = $"[{DateTime.Now:HH:mm:ss}] {fileName} (Line:{line}) {member}() : {message}";
        
        Console.WriteLine(logText);
    }
}

実行結果例

[12:00:01] Program.cs (Line:11) Main() : 処理を開始します。
[12:00:01] Program.cs (Line:19) ProcessData() : データを処理中...
[12:00:01] Program.cs (Line:28) ProcessData() : 例外発生: DB接続エラー

カスタムポイント

  • ログフォーマットの調整
    • CallerFilePath は絶対パス(例: C:\Users\...\Program.cs)を返すため、Path.GetFileName でファイル名だけにするか、プロジェクトルートからの相対パスに加工するとログが見やすくなります。
  • 引数の順序
    • これらの属性付き引数は「オプション引数」として扱われるため、メソッドの引数リストの末尾に配置するのが一般的です。

注意点

  1. デフォルト値の必須性
    • [CallerMemberName] などの属性をつける引数は、必ずデフォルト引数(= null= 0)として宣言する必要があります。これを忘れるとコンパイルエラーになります。
  2. セキュリティ(パスの漏洩)
    • CallerFilePath はコンパイル時の環境のフルパスを含みます。Webアプリケーションのエラー画面などでそのままユーザーに表示すると、サーバーのディレクトリ構成が漏洩するリスクがあります。
  3. コンパイル時の解決
    • これらの値は実行時(リフレクション等)ではなく、コンパイル時にリテラルとして埋め込まれます。したがって、難読化ツールを通しても元のソースコード上の情報が残る場合があります。

応用

WPF/MAUIなどのプロパティ変更通知

INotifyPropertyChanged の実装において、変更されたプロパティ名を文字列で渡す手間を省くために CallerMemberName が頻繁に利用されます。

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class UserViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    // プロパティ名を明示的に渡す必要がない
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private string _name;
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            // "Name" という文字列を書かずに済む
            OnPropertyChanged(); 
        }
    }
}

まとめ

Caller Information Attributes を活用することで、ログ出力コードに「現在地」を書く冗長な作業から解放されます。特にデバッグログの基盤を作成する際や、MVVMパターンでのプロパティ変更通知の実装において非常に強力な機能です。あくまでコンパイル時の情報を埋め込む仕組みであるため、実行時のパフォーマンスへの影響も最小限に抑えられます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次