如何拦截C#中的方法调用?
对于一个给定的类我想跟踪function,即我想logging每个方法调用(方法签名和实际参数值)和每个方法退出(只是方法签名)。
我如何做到这一点假设:
- 我不想为C#使用任何第三方AOP库,
- 我不想添加重复代码到所有我想跟踪的方法,
- 我不想改变类的公共API – 类的用户应该能够以完全相同的方式调用所有的方法。
为了使问题更具体,我们假设有三个类:
public class Caller { public static void Call() { Traced traced = new Traced(); traced.Method1(); traced.Method2(); } } public class Traced { public void Method1(String name, Int32 value) { } public void Method2(Object object) { } } public class Logger { public static void LogStart(MethodInfo method, Object[] parameterValues); public static void LogEnd(MethodInfo method); }
如何调用Logger.LogStart和Logger.LogEnd调用Method1和Method2 ,而不修改Caller.Call方法,也不显式添加调用到Traced.Method1和Traced.Method2 ?
编辑:如果允许稍微改变Call方法,那么解决scheme是什么?
C#不是面向AOP的语言。 它有一些AOPfunction,你可以效仿其他一些,但是用C#做AOP是痛苦的。
我想方设法做到你想做的事情,我发现没有简单的方法来做到这一点。
据我所知,这是你想要做的:
[Log()] public void Method1(String name, Int32 value);
为了做到这一点,你有两个主要的select
-
从MarshalByRefObject或ContextBoundObjectinheritance你的类,并定义一个从IMessageSinkinheritance的属性。 这篇文章有一个很好的例子。 你不得不考虑使用MarshalByRefObject的性能会下降,我的意思是,我说的是10倍的性能损失,所以仔细想一想,然后再尝试。
-
另一种select是直接注入代码。 在运行时,意味着你将不得不使用reflection来“读”每一个类,获得它的属性,并注入适当的调用(对于这个问题,我认为你不能使用Reflection.Emit方法,因为我认为Reflection.Emit wouldn不允许你在现有的方法中插入新的代码)。 在devise时,这将意味着创build一个CLR编译器的扩展,我真的不知道如何完成。
最后的select是使用IoC框架 。 也许这不是一个完美的解决scheme,因为大多数IoC框架通过定义允许钩住方法的入口点来工作,但取决于你想要存档的内容,这可能是一个公平的近似。
最简单的方法是使用PostSharp 。 它会根据您应用的属性在您的方法中注入代码。 它可以让你做到你想要的。
另一个select是使用分析API在代码中注入代码,但是这真的是硬核。
如果您编写一个类 – 将其称为“追踪”(Tracing) – 实现了IDisposable接口,则可以将所有方法体包装为一个
Using( Tracing tracing = new Tracing() ){ ... method body ...}
在Tracing类中,可以分别在Tracing类中处理构造函数/ Dispose方法中跟踪的逻辑,以跟踪方法的进入和退出。 这样的:
public class Traced { public void Method1(String name, Int32 value) { using(Tracing tracer = new Tracing()) { [... method body ...] } } public void Method2(Object object) { using(Tracing tracer = new Tracing()) { [... method body ...] } } }
看看这个 – 漂亮的东西.. http://msdn.microsoft.com/en-us/magazine/cc164165.aspx
必要的.net – 唐盒子有一个关于你需要什么叫拦截。 我在这里刮了一些(对不起字体的颜色 – 当时我有一个黑暗的主题…) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html
我find了一种不同的方式,可能会更容易…
声明一个方法调用方法
[WebMethod] public object InvokeMethod(string methodName, Dictionary<string, object> methodArguments) { try { string lowerMethodName = '_' + methodName.ToLowerInvariant(); List<object> tempParams = new List<object>(); foreach (MethodInfo methodInfo in serviceMethods.Where(methodInfo => methodInfo.Name.ToLowerInvariant() == lowerMethodName)) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length != methodArguments.Count()) continue; else foreach (ParameterInfo parameter in parameters) { object argument = null; if (methodArguments.TryGetValue(parameter.Name, out argument)) { if (parameter.ParameterType.IsValueType) { System.ComponentModel.TypeConverter tc = System.ComponentModel.TypeDescriptor.GetConverter(parameter.ParameterType); argument = tc.ConvertFrom(argument); } tempParams.Insert(parameter.Position, argument); } else goto ContinueLoop; } foreach (object attribute in methodInfo.GetCustomAttributes(true)) { if (attribute is YourAttributeClass) { RequiresPermissionAttribute attrib = attribute as YourAttributeClass; YourAttributeClass.YourMethod();//Mine throws an ex } } return methodInfo.Invoke(this, tempParams.ToArray()); ContinueLoop: continue; } return null; } catch { throw; } }
然后我这样定义我的方法
[WebMethod] public void BroadcastMessage(string Message) { //MessageBus.GetInstance().SendAll("<span class='system'>Web Service Broadcast: <b>" + Message + "</b></span>"); //return; InvokeMethod("BroadcastMessage", new Dictionary<string, object>() { {"Message", Message} }); } [RequiresPermission("editUser")] void _BroadcastMessage(string Message) { MessageBus.GetInstance().SendAll("<span class='system'>Web Service Broadcast: <b>" + Message + "</b></span>"); return; }
现在我可以在运行时检查没有dependency injection…
在网站没有窍门:)
希望你会同意,这是一个AOP框架,或从MarshalByRefObject派生或使用远程处理或代理类的权重较小。
首先你必须修改你的类来实现一个接口(而不是实现MarshalByRefObject)。
interface ITraced { void Method1(); void Method2() } class Traced: ITraced { .... }
接下来,您需要一个基于RealProxy的通用包装对象来装饰任何接口,以允许拦截对装饰对象的任何调用。
class MethodLogInterceptor: RealProxy { public MethodLogInterceptor(Type interfaceType, object decorated) : base(interfaceType) { _decorated = decorated; } public override IMessage Invoke(IMessage msg) { var methodCall = msg as IMethodCallMessage; var methodInfo = methodCall.MethodBase; Console.WriteLine("Precall " + methodInfo.Name); var result = methodInfo.Invoke(_decorated, methodCall.InArgs); Console.WriteLine("Postcall " + methodInfo.Name); return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall); } }
现在我们准备拦截对ITraced的Method1和Method2的调用
public class Caller { public static void Call() { ITraced traced = (ITraced)new MethodLogInterceptor(typeof(ITraced), new Traced()).GetTransparentProxy(); traced.Method1(); traced.Method2(); } }
我不知道解决scheme,但我的方法如下。
使用自定义属性来装饰类(或其方法)。 在程序的其他地方,让一个初始化函数反映所有types,读取用这些属性修饰的方法,并在方法中注入一些IL代码。 实际上用一个存根调用LogStart
(实际的方法,然后是LogEnd
replace方法可能更实际LogEnd
。 另外,我不知道是否可以使用reflection来改变方法,所以更换整个types可能更实际一些。
AOP是实现干净代码的必要条件,但是如果要在C#中包围块,generics方法的使用相对更简单。 (具有智能和强types的代码)当然,它不能成为AOP的替代品。
虽然PostSHarp几乎没有bug的问题(我不觉得在生产中使用自信),这是一个很好的东西。
generics包装类,
public class Wrapper { public static Exception TryCatch(Action actionToWrap, Action<Exception> exceptionHandler = null) { Exception retval = null; try { actionToWrap(); } catch (Exception exception) { retval = exception; if (exceptionHandler != null) { exceptionHandler(retval); } } return retval; } public static Exception LogOnError(Action actionToWrap, string errorMessage = "", Action<Exception> afterExceptionHandled = null) { return Wrapper.TryCatch(actionToWrap, (e) => { if (afterExceptionHandled != null) { afterExceptionHandled(e); } }); } }
用法可能是这样的(具有智力感)
var exception = Wrapper.LogOnError(() => { MessageBox.Show("test"); throw new Exception("test"); }, "Hata");
您可以在CodePlex上使用开源框架CInject 。 您可以编写最小的代码来创build一个Injector,并使用CInject快速拦截任何代码。 另外,因为这是开源的,你也可以扩展它。
或者你可以按照本文提到的使用IL拦截方法调用中提到的步骤,并使用C#中的Reflection.Emit类创build自己的拦截器。
您可以通过DI容器(如Castle Windsor)的 拦截function来实现此function。 的确,可以通过这样的方式configuration容器,使得每个具有由特定属性修饰的方法的类都将被拦截。
关于第三点,OP要求没有AOP框架的解决scheme。 我在下面的答案中假定应该避免的是Aspect,JointPoint,PointCut等。根据CastleWindsor的拦截文档 ,这些都不需要完成问题。
根据属性的存在configuration拦截器的通用注册:
public class RequireInterception : IContributeComponentModelConstruction { public void ProcessModel(IKernel kernel, ComponentModel model) { if (HasAMethodDecoratedByLoggingAttribute(model.Implementation)) { model.Interceptors.Add(new InterceptorReference(typeof(ConsoleLoggingInterceptor))); model.Interceptors.Add(new InterceptorReference(typeof(NLogInterceptor))); } } private bool HasAMethodDecoratedByLoggingAttribute(Type implementation) { foreach (var memberInfo in implementation.GetMembers()) { var attribute = memberInfo.GetCustomAttributes(typeof(LogAttribute)).FirstOrDefault() as LogAttribute; if (attribute != null) { return true; } } return false; } }
将创build的IContributeComponentModelConstruction添加到容器
container.Kernel.ComponentModelBuilder.AddContributor(new RequireInterception());
你可以在拦截器本身做任何你想做的事情
public class ConsoleLoggingInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { Console.Writeline("Log before executing"); invocation.Proceed(); Console.Writeline("Log after executing"); } }
将日志logging属性添加到要logging的方法中
public class Traced { [Log] public void Method1(String name, Int32 value) { } [Log] public void Method2(Object object) { } }
请注意,如果只需要拦截某个类的某些方法,则需要对该属性进行一些处理。 默认情况下,所有的公共方法将被拦截。
您可以使用GOF装饰模式,并“装饰”所有需要跟踪的类。
对于一个IOC容器来说,这可能是非常实用的(但是如果你打算沿着IOCpath走,你可能会想先考虑拦截方法)。
你需要错误Ayende为他如何做的答案: http : //ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx
- 编写你自己的AOP库。
- 使用reflection为您的实例生成一个日志代理(不确定是否可以在不更改现有代码的某些部分的情况下执行此操作)。
- 重写程序集并注入日志代码(基本上与1相同)。
- 主持CLR,并在这个级别添加日志(我认为这是最难实现的解决scheme,不知道你是否拥有CLR所需的钩子)。
如果你想跟踪你的方法没有限制(没有代码适应,没有AOP框架,没有重复的代码),让我告诉你,你需要一些魔术…
说真的,我解决了它在运行时实现AOP框架的问题。
你可以在这里find: NConcern .NET AOP框架
我决定创build这个AOP框架来回应这种需求。 这是一个非常轻量级的简单图书馆。 你可以在主页看到一个logging器的例子。
如果您不想使用第三方程序集,则可以浏览代码源(开放源代码)并复制Aspect.Directory.cs和Aspect.Directory.Entry.cs文件以根据您的意愿进行调整。 这些类允许在运行时replace你的方法。 我只要求你尊重许可证。
我希望你能find你所需要的,或者说服你最终使用AOP框架。
在C#6发布'nameof'之前,你可以做的最好的事情是使用缓慢的StackTrace和linqexpression式。
例如对于这样的方法
public void MyMethod(int age, string name) { log.DebugTrace(() => age, () => name); //do your stuff }
这样的行可能会在你的日志文件中产生
Method 'MyMethod' parameters age: 20 name: Mike
这是实现:
//TODO: replace with 'nameof' in C# 6 public static void DebugTrace(this ILog log, params Expression<Func<object>>[] args) { #if DEBUG var method = (new StackTrace()).GetFrame(1).GetMethod(); var parameters = new List<string>(); foreach(var arg in args) { MemberExpression memberExpression = null; if (arg.Body is MemberExpression) memberExpression = (MemberExpression)arg.Body; if (arg.Body is UnaryExpression && ((UnaryExpression)arg.Body).Operand is MemberExpression) memberExpression = (MemberExpression)((UnaryExpression)arg.Body).Operand; parameters.Add(memberExpression == null ? "NA" : memberExpression.Member.Name + ": " + arg.Compile().DynamicInvoke().ToString()); } log.Debug(string.Format("Method '{0}' parameters {1}", method.Name, string.Join(" ", parameters))); #endif }