Как мне найти тип экземпляра объекта, вызывающего текущую функцию? - PullRequest
7 голосов
/ 04 декабря 2008

В настоящее время у меня есть функция CreateLog () для создания журнала log4net с именем после класса создаваемого экземпляра. Обычно используется как в:

class MessageReceiver
{
     protected ILog Log = Util.CreateLog();
     ...
}

Если мы удалим много ошибок обработки, реализация сводится к: [РЕДАКТИРОВАТЬ: Пожалуйста, прочитайте более длинную версию CreateLog далее в этом посте.]

public ILog CreateLog()
{
        System.Diagnostics.StackFrame stackFrame = new System.Diagnostics.StackFrame(1);
        System.Reflection.MethodBase method = stackFrame.GetMethod();
        return CreateLogWithName(method.DeclaringType.FullName);
}

Проблема в том, что если мы переименуем MessageReceiver в подклассы, журнал все равно получит свое имя от MessageReceiver, поскольку это объявленный класс метода (конструктора), который вызывает CreateLog.

class IMReceiver : MessageReceiver
{ ... }

class EmailReceiver : MessageReceiver
{ ... }

Экземпляры обоих этих классов получат журналы с именем «MessageReceiver», в то время как я хотел бы, чтобы им были присвоены имена «IMReceiver» и «EmailReceiver».

Я знаю, что это легко сделать (и сделать), передав ссылку на объект в процессе создания при вызове CreateLog, поскольку метод GetType () для объекта делает то, что я хочу.

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

Есть ли кто-нибудь, кто может показать мне, как реализовать нулевой аргумент CreateLog (), который получает имя из подкласса, а не декларирующий класс?

EDIT:

Функция CreateLog делает больше, чем упомянуто выше. Причиной наличия одного журнала на экземпляр является возможность различаться между различными экземплярами в файле журнала. Это обеспечивается парой CreateLog / CreateLogWithName.

Расширение функциональности CreateLog () для мотивации его существования.

public ILog CreateLog()
{
        System.Diagnostics.StackFrame stackFrame = new System.Diagnostics.StackFrame(1);
        System.Reflection.MethodBase method = stackFrame.GetMethod();
        Type type = method.DeclaringType;

        if (method.IsStatic)
        {
            return CreateLogWithName(type.FullName);
        }
        else
        {
            return CreateLogWithName(type.FullName + "-" + GetAndInstanceCountFor(type));
        }
}

Также я предпочитаю писать ILog Log = Util.CreateLog (); вместо того, чтобы копировать какую-то длинную загадочную строку из другого файла всякий раз, когда я пишу новый класс. Я знаю, что отражение, используемое в Util.CreateLog, не гарантируется, хотя - гарантированно работает System.Reflection.MethodBase.GetCurrentMethod ()?

Ответы [ 5 ]

2 голосов
/ 04 декабря 2008

Думаю, вы задаете не тот вопрос. Прежде всего, регистратор должен быть статическим для каждого класса - каждый класс должен объявлять свой собственный регистратор (для обеспечения правильного представления имен классов И для обеспечения возможности выборочного сообщения сообщений журнала, отфильтрованных по проекту или пространству имен, из файла конфигурации.

Во-вторых, похоже, что вы создали этот метод исключительно для идентификации имени вызывающего класса? Если это так, мы используем этот стандартный код, который вставляется в каждый класс:

private static ILog log = 
log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

Поскольку это конфиденциально, вы гарантируете, что ваши наследующие классы должны объявить свой собственный регистратор, а не использовать ваш. Поскольку он статичен, вы гарантируете, что накладные расходы при поиске регистратора возникают только один раз.

Приношу свои извинения, если у вас были разные причины для кодирования метода Util.CreateLog ().

2 голосов
/ 04 декабря 2008

Обычно, MethodBase.ReflectedType будет иметь вашу информацию. Но, согласно MSDN StackFrame.GetMethod :

Метод, который в данный момент выполняется, может быть унаследован от базового класса, хотя он вызывается в производном классе. В этом случае свойство ReflectedType объекта MethodBase, которое возвращается GetMethod, идентифицирует базовый класс, а не производный класс.

что означает, что вам, вероятно, не повезло.

1 голос
/ 04 декабря 2008

Есть ли кто-нибудь, кто может показать мне, как реализовать нулевой аргумент CreateLog (), который получает имя из подкласса, а не декларирующий класс?

Не думаю, что вы сможете сделать это, посмотрев на кадр стека.

Пока ваш класс IMReceiver, вызов метода CreateLog находится в классе MessageReceiver. Фрейм стека должен сообщать вам, откуда вызывается метод, иначе он не будет использоваться, поэтому он всегда скажет MessageReceiver

Если вы вызвали CreateLog явно в вашем IMReceiver и других классах, то это работает, так как кадр стека показывает метод, вызываемый в производном классе (потому что это действительно так).

Вот лучшее, что я могу придумать:

class BaseClass{
  public Log log = Utils.CreateLog();
}
class DerivedClass : BaseClass {
  public DerivedClass() {
    log = Utils.CreateLog();
  }
}

Если мы проследим создание журналов, мы получим это:

new BaseClass();
# Log created for BaseClass

new DerivedClass();
# Log created for BaseClass
# Log created for DerivedClass

Второй «журнал, созданный для производного класса» перезаписывает переменную экземпляра, поэтому ваш код будет работать правильно, вы просто создадите журнал BaseClass, который сразу же будет удален. Это кажется хакерским и плохим для меня, я бы просто пошел с указанием параметра типа в конструкторе или с использованием универсального.

ИМХО указание типа чище, чем ковыряться в кадре стека в любом случае

Если вы можете получить его, не глядя на кадр стека, ваши возможности значительно расширятся

0 голосов
/ 13 марта 2009

Пройдитесь по стеку, проверяя связь между базовым классом и производным классом.

        var type = new StackFrame(1).GetMethod().DeclaringType;
        foreach (var frame in new StackTrace(2).GetFrames())
            if (type != frame.GetMethod().DeclaringType.BaseType)
                break;
            else
                type = frame.GetMethod().DeclaringType;
        return CreateLogWithName(type.FullName);

Возможно, вы захотите проверить, что рассмотренные методы являются конструкторами. Но сценарий, в котором подкласс создает экземпляр суперкласса в методе, отличном от его конструктора, может по-прежнему хотеть текущего поведения.

0 голосов
/ 04 декабря 2008

Попробуйте метод StackTrace.GetFrames. Он возвращает массив всех объектов StackFrame в стеке вызовов. Ваш абонент должен быть на первом месте.

class Program
{
    static void Main(string[] args)
    {
        Logger logger = new Logger();
        Caller caller = new Caller();
        caller.FirstMethod(logger);
        caller.SecondMethod(logger);
    }
}

public class Caller
{
    public void FirstMethod(Logger logger)
    {
        Console.Out.WriteLine("first");
        logger.Log();
    }

    public void SecondMethod(Logger logger)
    {
        Console.Out.WriteLine("second");
        logger.Log();
    }
}

public class Logger
{
    public void Log()
    {
        StackTrace trace = new StackTrace();
        var frames = trace.GetFrames();
        Console.Out.WriteLine(frames[1].GetMethod().Name);
    }
}

это выводит

первый FirstMethod второй SecondMethod

...