2008-12-03 9 views
7

Actualmente tengo la función CreateLog() para crear un log4net Log con nombre después de la clase de la instancia de construcción. Normalmente se utiliza como en:¿Cómo encuentro el tipo de instancia de objeto de la persona que llama de la función actual?

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

Si quitamos un montón de manejo de la aplicación de error se reduce a: [EDIT:. Por favor leer la versión más larga de CreateLog más adelante en este post]

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

El problema es que si incluimos MessageReceiver en subclases, el registro seguirá tomando su nombre de MessageReceiver ya que esta es la clase de declaración del método (constructor) que llama a CreateLog.

class IMReceiver : MessageReceiver 
{ ... } 

class EmailReceiver : MessageReceiver 
{ ... } 

Las instancias de ambas clases obtendrían los registros con el nombre "MessageReceiver", mientras que me gustaría que fueran nombres dados "IMReceiver" y "EmailReceiver".

Sé que esto se puede hacer fácilmente (y se hace) pasando una referencia al objeto en la creación al llamar a CreateLog ya que el método GetType() en el objeto hace lo que quiero.

Existen algunas razones menores para preferir no agregar el parámetro y personalmente me molesta no encontrar una solución sin argumentos adicionales.

¿Hay alguien que pueda mostrarme cómo implementar un argumento cero CreateLog() que obtiene el nombre de la subclase y no de la clase declarante?

EDIT:

La función CreateLog hace más que se mencionó anteriormente. La razón para tener un registro por instancia es poder diferir entre diferentes instancias en el archivo de registro. Esto se aplica mediante el par CreateLog/CreateLogWithName.

Ampliando la funcionalidad de CreateLog() para motivar su existencia.

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)); 
     } 
} 

También prefiero escribir ILog Log = Util.CreateLog(); en lugar de copiar en una línea críptica larga de otro archivo cada vez que escribo una nueva clase. Soy consciente de que la reflexión utilizada en Util.CreateLog no está garantizada, ¿está garantizado que funciona System.Reflection.MethodBase.GetCurrentMethod()?

Respuesta

1

¿Hay alguien que pueda mostrarme cómo implementar un argumento cero CreateLog() que obtiene el nombre de la subclase y no de la clase declarante?

No creo que pueda hacerlo mirando el marco de la pila.

Mientras su clase es IMReceiver, la llamada al método CreateLog está en la clase MessageReceiver. El marco de pila debe decirle donde el método está siendo llamado desde, o no lo sería cualquier uso, por lo que siempre va a decir MessageReceiver

Si llamó CreateLog explícitamente en sus IMReceiver y otras clases, entonces funciona, como el marco de pila muestra el método que se llama en la clase derivada (porque en realidad lo es).

Aquí es lo mejor que puedo llegar a:

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

Si rastreamos creación de registros, obtenemos lo siguiente:

new BaseClass(); 
# Log created for BaseClass 

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

El segundo 'registro creado para la clase derivada' sobrescribe el variable de instancia, por lo que su código se comportará correctamente, solo estará creando un registro de BaseClass que inmediatamente se descarta. Esto me parece malhumorado y malo, solo me gustaría especificar el parámetro de tipo en el constructor o usar un genérico.

mi humilde opinión, especificando el tipo es más limpio que el hurgar en el marco de pila de todos modos

Si lo puede conseguir sin mirar el marco de pila, las opciones de ampliar considerablemente

2

Normalmente, MethodBase.ReflectedType tendría su información. Pero, según MSDN StackFrame.GetMethod:

El método que se está ejecutando actualmente se puede heredar de una clase base, aunque se llama en una clase derivada. En este caso, la propiedad ReflectedType del objeto MethodBase que GetMethod devuelve identifica la clase base, no la clase derivada.

lo que significa que probablemente no tenga suerte.

0

Pruebe el método StackTrace.GetFrames. Devuelve una matriz de todos los objetos StackFrame en la pila de llamadas. Su interlocutor debería estar en el índice uno.

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); 
    } 
} 

esta salida a

primera FirstMethod segundo SecondMethod

+0

System.Diagnostics.StackFrame stackFrame = new System.Diagnostics.StackFrame (1); debe ser el mismo que: System.Diagnostics.StackFrame [] frames = new System.Diagnostics.StackTrace.GetFrames(); System.Diagnostics.StackFrame stackFrame = frames [1]; – notso

+0

eso es cierto, solo pensé que mostrarle cómo obtener toda la matriz en una sola llamada podría ser útil. –

2

Creo que se le puede pedir la pregunta equivocada. En primer lugar, el registrador debe ser estático para cada clase: cada clase debe declarar su propio registrador (para garantizar que los nombres de clase se notifiquen adecuadamente Y para permitir el informe selectivo de los mensajes de registro filtrados por proyecto o espacio de nombres, desde el archivo de configuración

en segundo lugar, parece que ha creado este método únicamente para identificar el nombre de la clase llamada Si lo que utilizar este código reutilizable que se pega en cada clase:?

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

Debido a que es privada a asegurar que su heredera las clases deben declarar su propio registrador y no usar el suyo. Debido a que es estático, asegúrese de que la sobrecarga de buscar el registrador solo se incurre una vez.

Mis disculpas si tenía motivos diferentes para codificar el método Util.CreateLog().

+0

Lo convertí en un miembro protegido porque quiero que las clases de consejería usen el mismo Log. Mi objetivo es poder conectar entradas de registro desde la misma instancia de objeto. – notso

0

Mejora la comprobación de la pila para la relación de clase derivada de clase base.

 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); 

Es posible que desee comprobar que los métodos examinados son constructores. Pero un escenario donde la subclase está creando una instancia de la superclase en un método distinto de su constructor, aún puede querer el comportamiento actual.

+0

Desafortunadamente, creo que cualquier uso de stacktrace es no-no. Cuando mi código en la pregunta se ejecutó con compilación de lanzamiento, no pasó mucho tiempo hasta que se rompió debido a que los marcos de pila se optimizaban. – notso

Cuestiones relacionadas