Stack Overflow上的AOP .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,或者免费提供,那就太好了)
答案 0 :(得分: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;
}
如果有人知道如何更好地完成这项工作,我仍然对答案感兴趣,我不会将此标记为解决方案 - 除非没有更简单的解决方案出现。
答案 1 :(得分:4)
我不确定你从哪里得到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只根据它们被装饰的属性将代码注入某些方法。如果查看this tutorial,它只会显示对类型和方法名称进行过滤的方法。我们通过将要检查的类型传递给属性来解决这个问题,然后在OnEntry
中,您可以使用反射来查找属性并决定是否记录。结果是缓存的,因此您只需在第一次调用时执行此操作。
我调整了上面的代码来证明这个想法。