目前我有 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() 来从子类而不是声明类获取名称?
编辑:
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() 保证工作吗?
通常,MethodBase.ReflectedType 会有您的信息。但是,根据 MSDN StackFrame.GetMethod:
当前正在执行的方法可能是从基类继承的,尽管它是在派生类中调用的。在这种情况下,GetMethod 返回的 MethodBase 对象的 ReflectedType 属性标识基类,而不是派生类。
这意味着你可能不走运。
我认为您可能问错了问题。首先,记录器对于每个类应该是静态的 - 每个类应该声明自己的记录器(以确保正确报告类名并允许从配置文件中选择性地报告按项目或命名空间过滤的日志消息。
其次,您创建此方法似乎只是为了识别调用类的名称?如果是这样,我们使用粘贴到每个类中的样板代码:
private static ILog log =
log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
因为它是私有的,所以您确保您的继承类必须声明自己的记录器而不是使用您的记录器。因为它是静态的,所以您可以确保查找记录器的开销仅产生一次。
如果您有不同的原因编码 Util.CreateLog() 方法,我深表歉意。
有谁可以向我展示如何实现零参数 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 日志。这对我来说似乎很糟糕,我只是在构造函数中指定类型参数或使用泛型。
恕我直言,指定类型比在堆栈帧中闲逛更干净
如果您无需查看堆栈框架即可获得它,那么您的选择范围就会大大扩展
尝试 StackTrace.GetFrames 方法。它返回调用堆栈中所有 StackFrame 对象的数组。您的呼叫者应该位于索引 1。
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);
}
}
这个输出
首先 第一种方法 第二 第二种方法
沿着堆栈检查基类-派生类关系。
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);
您可能需要检查所检查的方法是否为构造函数。但是,如果子类在构造函数之外的方法中实例化超类,则可能仍然需要当前的行为。