Avatar ·

Как получить имя текущего класса в C# (собственное имя класса)?

Всем привет! Я начинающий C# разработчик и столкнулся с такой проблемой. Пишу небольшой логгер для своего приложения, и мне нужно, чтобы в каждом сообщении лога автоматически указывалось имя класса, из которого был вызван метод логирования. Пробую разные способы, но ничего не работает как надо. Вот мой код: ```csharp public class MyClass { public void DoSomething() { Logger.Log("Начало операции"); // ... какой-то код } } public static class Logger { public static void Log(string message) { // Хочу получить "MyClass" здесь string className = ???? ; // что сюда писать? Console.WriteLine($"[{className}] {message}"); } } ``` Я пробовал использовать `typeof(MyClass).Name`, но это жестко зашивает имя класса, и если я скопирую этот код в другой класс, то придется менять. Пытался через `MethodBase.GetCurrentMethod().DeclaringType.Name`, но это возвращает "Logger", а не "MyClass". Еще видел где-то `this.GetType().Name`, но в статическом методе `this` нет. Подскажите, как правильно узнать имя класса, который вызвал метод? Может быть есть какой-то универсальный способ через рефлексию? Буду благодарен за примеры кода. <<>>

Получение имени вызывающего класса через StackTrace

Самый простой и распространённый способ — использовать класс StackTrace из пространства имён System.Diagnostics. Он позволяет получить информацию о стеке вызовов и узнать, какой метод и класс вызвали текущий метод.

Вот как это работает для вашего логгера:

using System;
using System.Diagnostics;

public static class Logger
{
    public static void Log(string message)
    {
        // Получаем стек вызовов
        StackTrace stackTrace = new StackTrace();
        
        // Фрейм с индексом 1 — это тот, кто вызвал Log (индекс 0 — сам Log)
        StackFrame callingFrame = stackTrace.GetFrame(1);
        
        // Получаем метод, который вызвал Log
        var callingMethod = callingFrame.GetMethod();
        
        // Получаем тип (класс), которому принадлежит этот метод
        string className = callingMethod.DeclaringType.Name;
        
        Console.WriteLine($"[{className}] {message}");
    }
}

public class MyClass
{
    public void DoSomething()
    {
        Logger.Log("Начало операции"); // Выведет: [MyClass] Начало операции
    }
}

public class AnotherClass
{
    public void Process()
    {
        Logger.Log("Обработка данных"); // Выведет: [AnotherClass] Обработка данных
    }
}

Важные моменты:

  • Индекс фрейма: GetFrame(0) — это сам метод Log, GetFrame(1) — вызывающий метод, GetFrame(2) — метод, вызвавший вызывающий и т.д.
  • Производительность: Создание StackTrace — относительно дорогая операция. Для часто вызываемых методов (например, в цикле) это может быть проблемой.
  • Оптимизации JIT: В релизной сборке компилятор может встроить (inline) метод Log, и стек вызовов исказится. Чтобы этого избежать, можно добавить атрибут [MethodImpl(MethodImplOptions.NoInlining)] к методу Log.

Оптимизированная версия с атрибутом:

using System.Runtime.CompilerServices;

public static class Logger
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    public static void Log(string message)
    {
        StackTrace stackTrace = new StackTrace();
        StackFrame callingFrame = stackTrace.GetFrame(1);
        string className = callingFrame.GetMethod().DeclaringType.Name;
        Console.WriteLine($"[{className}] {message}");
    }
}

Альтернатива через CallerMemberName (если нужно только имя метода):

Если вам нужно имя вызывающего метода (а не класса), можно использовать атрибут [CallerMemberName] — он работает без рефлексии и быстрее:

public static void Log(string message, [CallerMemberName] string callerName = "")
{
    Console.WriteLine($"[{callerName}] {message}");
}

Но для получения имени класса этот атрибут не подходит — он передаёт только имя метода.

В вашем случае рекомендую использовать StackTrace с индексом 1 и атрибутом NoInlining — это надёжное и понятное решение.

<<>>

Использование CallerFilePath и парсинг имени класса

Ещё один подход, который может быть полезен, если вы не хотите использовать StackTrace (например, из-за производительности или особенностей JIT). Компилятор C# предоставляет атрибуты CallerFilePath, CallerLineNumber и CallerMemberName, которые подставляются на этапе компиляции без накладных расходов во время выполнения.

Хотя CallerFilePath передаёт полный путь к файлу, а не имя класса, вы можете извлечь имя класса из имени файла, если соблюдаете соглашение: файл называется так же, как класс (например, MyClass.cs → класс MyClass).

using System;
using System.IO;

public static class Logger
{
    public static void Log(string message, [CallerFilePath] string filePath = "")
    {
        // Извлекаем имя файла без расширения
        string fileName = Path.GetFileNameWithoutExtension(filePath);
        Console.WriteLine($"[{fileName}] {message}");
    }
}

public class MyClass
{
    public void DoSomething()
    {
        Logger.Log("Начало операции"); // Выведет: [MyClass] Начало операции
    }
}

Плюсы и минусы:

  • + Очень быстро — не использует рефлексию и не создаёт стек вызовов.
  • + Не подвержено проблемам с JIT-встраиванием.
  • - Работает только если имя файла совпадает с именем класса (что обычно верно, но не гарантируется).
  • - Не сработает для частичных классов, вложенных классов или если файл переименован.

Этот метод хорошо подходит для простых логгеров или отладочной печати, где важна производительность, а точность не критична.