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

本文关键字:方法 属性 最简单 自定义属性 代码 注入 有没有 | 更新日期: 2023-09-27 18:01:58

在Stack Overflow上有很多关于。net中AOP的问题和答案,经常提到PostSharp和其他第三方产品。因此,在。net和c#世界中似乎有相当多的AOP选项。但是每一个都有它们的限制,在下载了有前途的PostSharp 之后,我在他们的文档中发现"方法必须是虚拟的",以便能够注入代码(编辑:参见ChrisWue的回答和我的评论-虚拟约束一定是在其中一个竞争者上,我想)。我还没有进一步调查这句话的准确性,但它的分类性让我回到了Stack Overflow。

所以我想得到这个非常具体问题的答案:

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

最简单的方法是什么?我偶然发现了Mono。Cecil,它看起来很理想,除了你似乎必须编写你想要注入IL的代码。这不是一个大问题,使用Mono很容易。Cecil获得用c#编写的代码的IL版本。但是,如果有更简单的东西,理想情况下,甚至内置到。net(我还在使用。net 3.5),我想知道。[更新:如果建议的工具不是。net框架的一部分,如果它是开源的就好了,比如Mono。

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

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

以下是我使用的3个文档来源:

  • static-method-interception-in-net-with-c-and-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<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;
}

如果有人知道一个更简单的方法来完成这个任务,我仍然对答案感兴趣,我不会把这个标记为解决方案-除非没有更简单的解决方案出现。

我不知道你从哪里得到的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,但我当然不希望这样-这会使它不那么有用。

Update:我不确定你是否可以很容易地说服Postsharp只根据它们被装饰的属性将代码注入某些方法。如果你看一下本教程,它只展示了过滤类型和方法名的方法。我们通过将我们想要检查的类型传递到属性中来解决这个问题,然后在OnEntry中,您可以使用反射来查找属性并决定是否记录。结果是缓存的,所以你只需要在第一次调用时做。

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