将代码注入到没有自定义属性的所有方法和属性的最简单方法

在Stack Overflow上有很多关于AOP in .NET的问题和解答,经常提到PostSharp和其他第三方产品。 因此,在.NET和C#世界中似乎有相当多的AOP选项。 但是每个都有它们的限制,并且在下载了有希望的PostSharp后, 我在他们的文档中发现“方法必须是虚拟的”才能够注入代码 (编辑:请参阅ChrisWue的答案和我的评论 – 虚拟约束必须有我认为,其中一个竞争者。 我没有进一步研究这个陈述的准确性,但是它的分类性让我回到Stack Overflow。

所以我想回答这个非常具体的问题:

我想在我的项目中没有自定义的 每个方法和属性 (静态,密封,内部,虚拟,非虚拟,无关紧要) 注入简单的 “if(some-condition)Console.WriteLine”样式代码 注释 ,以便在运行时动态测试我的软件。 这个注入的代码不应该保留在发布版本中,它只是用于在开发期间进行动态测试(与线程相关)。

最简单的方法是什么? 我偶然发现Mono.Cecil ,它看起来很理想,除了你似乎必须编写你想在IL中注入的代码。 这不是一个大问题,使用Mono.Cecil很容易获得用C#编写的IL版本的代码。 但是,如果有更简单的东西,理想情况下甚至可以内置到.NET中(我仍然在.NET 3.5上),我想知道。 [更新:如果建议的工具不是.NET Framework的一部分,那么如果它是开源的,如Mono.Cecil,或者免费提供,那就太好了]

我能用Mono.Cecil解决问题。 我仍然惊讶于它易于学习,易于使用和强大。 几乎完全缺乏文档并没有改变这一点。

这些是我使用的3个文档来源:

  • 静态方法拦截式净用-C-和-monocecil
  • 迁移到0.9
  • 源代码本身

第一个链接提供了一个非常温和的介绍,但由于它描述了较早版本的Cecil – 并且在此期间发生了很大变化 – 第二个链接对于翻译Cecil 0.9的介绍非常有帮助。 在开始之后,(也没有记录的)源代码是非常宝贵的,并且回答了我所遇到的每一个问题 – 一般都是关于.NET平台的那些问题,但是我确信在网上有大量的书籍和资料。

我现在可以获取DLL或EXE文件,修改它,然后将其写回磁盘。 我还没有做的唯一事情是弄清楚如何保持调试信息 – 文件名,行号等在编写DLL或EXE文件后当前会丢失。 我的背景不是.NET,所以我猜这里,我的猜测是我需要查看mono.cecil.pdb来解决这个问题。 某个地方 – 现在对我来说并不是那么重要。 我正在创建这个EXE文件,运行应用程序 – 这是一个复杂的GUI应用程序,经过多年的发展,带着你期望在这样一个,咳嗽的软件中找到的所有包袱 – 它会检查事情并记录错误我。

这是我的代码的要点:

 DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver(); // so it won't complain about not finding assemblies sitting in the same directory as the dll/exe we are going to patch assemblyResolver.AddSearchDirectory(assemblyDirectory); var readerParameters = new ReaderParameters { AssemblyResolver = assemblyResolver }; AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyFilename, readerParameters); foreach (var moduleDefinition in assembly.Modules) { foreach (var type in ModuleDefinitionRocks.GetAllTypes(moduleDefinition)) { foreach (var method in type.Methods) { if (!HasAttribute("MyCustomAttribute", method.method.CustomAttributes) { ILProcessor ilProcessor = method.Body.GetILProcessor(); ilProcessor.InsertBefore(method.Body.Instructions.First(), ilProcessor.Create(OpCodes.Call, threadCheckerMethod)); // ... private static bool HasAttribute(string attributeName, IEnumerable customAttributes) { return GetAttributeByName(attributeName, customAttributes) != null; } private static CustomAttribute GetAttributeByName(string attributeName, IEnumerable customAttributes) { foreach (var attribute in customAttributes) if (attribute.AttributeType.FullName == attributeName) return attribute; return null; } 

如果有人知道如何完成这项工作的简单方法,我仍然对答案感兴趣,我不会将此标记为解决方案 – 除非没有更简单的解决方案出现。

我不确定你的methods have to be virtual的。 我们使用Postsharp来定时调用WCF服务接口实现,并使用OnMethodBoundaryAspect创建一个我们可以用它来装饰类的属性。 快速示例:

 [Serializable] public class LogMethodCallAttribute : OnMethodBoundaryAspect { public Type FilterAttributeType { get; set; } public LogMethodCallAttribute(Type filterAttributeType) { FilterAttributeType = filterAttributeType; } public override void OnEntry(MethodExecutionEventArgs eventArgs) { if (!Proceed(eventArgs)) return; Console.WriteLine(GetMethodName(eventArgs)); } public override void OnException(MethodExecutionEventArgs eventArgs) { if (!Proceed(eventArgs)) return; Console.WriteLine(string.Format("Exception at {0}:\n{1}", GetMethodName(eventArgs), eventArgs.Exception)); } public override void OnExit(MethodExecutionEventArgs eventArgs) { if (!Proceed(eventArgs)) return; Console.WriteLine(string.Format("{0} returned {1}", GetMethodName(eventArgs), eventArgs.ReturnValue)); } private string GetMethodName(MethodExecutionEventArgs eventArgs) { return string.Format("{0}.{1}", eventArgs.Method.DeclaringType, eventArgs.Method.Name); } private bool Proceed(MethodExecutionEventArgs eventArgs) { return Attribute.GetCustomAttributes(eventArgs.Method, FilterAttributeType).Length == 0; } } 

然后我们这样:

  [LogMethodCallAttribute(typeof(MyCustomAttribute))] class MyClass { public class LogMe() { } [MyCustomAttribute] public class DoNotLogMe() { } } 

在Postsharp 1.5.6中,无需使用虚拟方法就可以像魅力一样工作。 也许他们已经改变了2.x但我当然不希望如此 – 这会使它变得不那么有用。

更新 :我不确定你是否可以轻易地说服Postsharp只根据它们被装饰的属性将代码注入某些方法。 如果您查看本教程,它只会显示对类型和方法名称进行过滤的方法。 我们通过将要检查的类型传递给属性来解决这个问题,然后在OnEntry您可以使用reflection来查找属性并决定是否记录。 结果是缓存的,因此您只需在第一次调用时执行此操作。

我调整了上面的代码来演示这个想法。