上周,我的Windows Service应用程序到生产环境的部署失败,因此我尝试以发布模式在本地运行该项目,并得到了InvalidOperationException: Can not determine current class.
,其追溯到对GetCurrentClassLogger
的调用。
我的项目使用Ninject将ILoggerFactory
解析为中间层的每个服务。然后,服务使用GetCurrentClassLogger()
在构造函数中获取正确的ILogger
。
例如:
public class FooService : IFooService
{
private readonly ILogger logger;
public FooService(ILoggerFactory loggerFactory)
{
this.logger = loggerFactory.GetCurrentClassLogger();
}
// ...
}
如果我在Ninject.Extensions.Logging的
GetCurrentClassLogger
处研究LoggerFactoryBase
的实现:[MethodImpl(MethodImplOptions.NoInlining)]
public ILogger GetCurrentClassLogger()
{
StackFrame stackFrame = new StackFrame(0, false);
if (stackFrame.GetMethod() == (MethodBase) LoggerFactoryBase.getCurrentClassLoggerMethodInfo)
stackFrame = new StackFrame(1, false);
Type declaringType = stackFrame.GetMethod().DeclaringType;
if (declaringType == (Type) null)
throw new InvalidOperationException(string.Format("Can not determine current class. Method: {0}", (object) stackFrame.GetMethod()));
return this.GetLogger(declaringType);
}
我可以看到在哪里引发了异常。
我的第一个直觉是检查是否由系统,框架或项目更新引起,但是自上次成功部署以来,没有做任何有意义的事情。
现在...关于此问题的“有趣”之处在于,当我在构造函数中添加带有
Trace.WriteLine("Test");
的行时,GetCurrentClassLogger
执行得很好。我能够将此问题与编译器的代码优化联系起来。如果我在项目属性的“构建”选项卡中将其禁用,它也可以正常执行。
问题:什么可能导致
StackFrame
停止提供DeclaringType
?同时,我将其用作解决方法,但我更喜欢使用原始方法:
this.logger = loggerFactory.GetLogger(this.GetType());
任何帮助将非常感激。
最佳答案
如果不看待CIL的发出,很难确切说明为什么行为不同,但是我猜想它已经内联了。
如果将构造函数的属性分配为:
[MethodImpl(MethodImplOptions.NoInlining)]
就像NInject代码正在做的一样。该方法可能内联,因为它只是分配了只读字段的一行。您可以在此处阅读一些有关内联如何影响StackFrames的信息:http://www.hanselman.com/blog/ReleaseISNOTDebug64bitOptimizationsAndCMethodInliningInReleaseBuildCallStacks.aspx
就是说,这种不可预测性是为什么使用堆栈框架是一个坏主意的原因。如果您希望不必指定类名的语法,正在使用VS 2012,并且文件名与类名匹配,则可以在扩展方法上使用CallerFilePath属性,并保留与现在相同的行为,而无需使用堆叠框架。看到:
https://msdn.microsoft.com/en-us/library/hh534540.aspx
更新:
一种替代方法可能是直接注入ILogger,并让您的容器负责确定要注入的组件的类型(因为它肯定知道)。这似乎也更干净。从此处查看示例:https://github.com/ninject/ninject.extensions.logging/issues/19
this.Bind<ILogger>().ToMethod(context =>
{
var typeForLogger = context.Request.Target != null
? context.Request.Target.Member.DeclaringType
: context.Request.Service;
return context.Kernel.Get<ILoggerFactory>().GetLogger(typeForLogger);
});