目次
概要
メソッドが「どこから」「誰によって」呼び出されたかをプログラム内で特定する実装です。 .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でファイル名だけにするか、プロジェクトルートからの相対パスに加工するとログが見やすくなります。
- 引数の順序
- これらの属性付き引数は「オプション引数」として扱われるため、メソッドの引数リストの末尾に配置するのが一般的です。
注意点
- デフォルト値の必須性
[CallerMemberName]などの属性をつける引数は、必ずデフォルト引数(= nullや= 0)として宣言する必要があります。これを忘れるとコンパイルエラーになります。
- セキュリティ(パスの漏洩)
CallerFilePathはコンパイル時の環境のフルパスを含みます。Webアプリケーションのエラー画面などでそのままユーザーに表示すると、サーバーのディレクトリ構成が漏洩するリスクがあります。
- コンパイル時の解決
- これらの値は実行時(リフレクション等)ではなく、コンパイル時にリテラルとして埋め込まれます。したがって、難読化ツールを通しても元のソースコード上の情報が残る場合があります。
応用
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パターンでのプロパティ変更通知の実装において非常に強力な機能です。あくまでコンパイル時の情報を埋め込む仕組みであるため、実行時のパフォーマンスへの影響も最小限に抑えられます。
