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

Eug*_*sky 9 .net c# aop code-injection

在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,或者免费提供,那就太好了]

Eug*_*sky 11

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

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

第一个链接提供了一个非常温和的介绍,但由于它描述了较早版本的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<CustomAttribute> customAttributes)
{
    return GetAttributeByName(attributeName, customAttributes) != null;
}

private static CustomAttribute GetAttributeByName(string attributeName, IEnumerable<CustomAttribute> customAttributes)
{
    foreach (var attribute in customAttributes)
        if (attribute.AttributeType.FullName == attributeName)
            return attribute;
    return null;
}
Run Code Online (Sandbox Code Playgroud)

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