C# 将代码注入所有不使用';没有自定义属性

C# 将代码注入所有不使用';没有自定义属性,c#,.net,aop,code-injection,C#,.net,Aop,Code Injection,这里有很多关于StackOverflow的问题和答案,经常提到PostSharp和其他第三方产品。因此,在.NET和C#世界中,似乎有相当多的AOP光电子。但每种方法都有其局限性,在下载了有前途的PostSharp后,我在他们的文档中发现“方法必须是虚拟的”,以便能够注入代码(编辑:请参见ChrisWue的回答和我的评论——我想虚拟的局限性必须在其中一个竞争者身上)。我没有进一步调查这句话的准确性,但它的明确性让我回到了StackOverflow 所以我想回答这个非常具体的问题: 我想在我的项目

这里有很多关于StackOverflow的问题和答案,经常提到PostSharp和其他第三方产品。因此,在.NET和C#世界中,似乎有相当多的AOP光电子。但每种方法都有其局限性,在下载了有前途的PostSharp后,我在他们的文档中发现“方法必须是虚拟的”,以便能够注入代码(编辑:请参见ChrisWue的回答和我的评论——我想虚拟的局限性必须在其中一个竞争者身上)。我没有进一步调查这句话的准确性,但它的明确性让我回到了StackOverflow

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

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


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

我不确定你从哪里得到的
方法必须是虚拟的。我们使用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仅根据修饰属性向某些方法中注入代码。如果您仔细看,它只显示了对类型和方法名进行过滤的方法。我们通过将要检查的类型传递到属性中解决了这个问题,然后在
onetry
中,您可以使用反射来查找属性并决定是否记录。该操作的结果会被缓存,因此您只需在第一次调用时执行该操作


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

我能够用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;
}
DefaultAssemblyResolver assemblyResolver=新的DefaultAssemblyResolver();
//因此,它不会抱怨没有找到与我们要修补的dll/exe位于同一目录中的程序集
assemblyResolver.AddSearchDirectory(assemblyDirectory);
var readerParameters=new readerParameters{AssemblyResolver=AssemblyResolver};
AssemblyDefinition assembly=AssemblyDefinition.ReadAssembly(assemblyFilename,readerParameters);
foreach(assembly.Modules中的var moduleDefinition)
{
foreach(ModuleDefinitionRocks.GetAllTypes(moduleDefinition)中的变量类型)
{
foreach(type.Methods中的var方法)
{
如果(!HasAttribute(“MyCustomAttribute”,method.method.CustomAttributes)
{
ILProcessor ILProcessor=method.Body.GetILProcessor();
ilProcessor.InsertBefore(method.Body.Instructions.First(),ilProcessor.Create(OpCodes.Call,threadCheckerMethod));
// ...
私有静态bool hasaAttribute(字符串attributeName、IEnumerable customAttributes)
{
返回GetAttributeByName(attributeName,customAttributes)!=null;
}
私有静态CustomAttribute GetAttributeByName(字符串attributeName,IEnumerable customAttributes)
{
foreach(customAttributes中的var属性)
if(attribute.AttributeType.FullName==attributeName)
返回属性;
返回null;
}

如果有人知道一种更简单的方法来完成这项工作,我仍然对答案感兴趣,我不会把它作为解决方案——除非没有更简单的解决方案出现。

克里斯,你说得对,我明白了