题
对于给定的类,我希望具有跟踪功能,即我想记录每个方法调用(方法签名和实际参数值)和每个方法退出(仅方法签名)。
假设:我如何实现这一点?
- 我不想为C#使用任何第三方AOP库,
- 我不想向我想要跟踪的所有方法添加重复的代码,
- 我不想更改该类的公共 API - 该类的用户应该能够以完全相同的方式调用所有方法。
为了让问题更具体,我们假设有 3 个类:
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);
}
我如何调用 记录器.LogStart 和 记录器.LogEnd 每次致电 方法1 和 方法2 不修改 呼叫者呼叫 方法并且无需显式添加调用 追踪方法1 和 追踪方法2?
编辑:如果允许我稍微改变 Call 方法,解决方案是什么?
解决方案
C# 不是面向 AOP 的语言。它有一些 AOP 功能,您可以模仿其他一些功能,但用 C# 制作 AOP 很痛苦。
我寻找方法来准确地做你想做的事情,但我发现没有简单的方法可以做到这一点。
据我了解,这就是您想要做的:
[Log()]
public void Method1(String name, Int32 value);
为了做到这一点,你有两个主要选择
从 MarshalByRefObject 或 ContextBoundObject 继承您的类,并定义一个从 IMessageSink 继承的属性。 本文 有一个很好的例子。尽管如此,您必须考虑到使用 MarshalByRefObject 性能会急剧下降,我是说真的,我说的是性能损失 10 倍,所以在尝试之前请仔细考虑。
另一种选择是直接注入代码。在运行时,这意味着您必须使用反射来“读取”每个类,获取其属性并注入适当的调用(就此而言,我认为您不能使用 Reflection.Emit 方法,因为我认为 Reflection.Emit 不会不允许您在现有方法中插入新代码)。在设计时,这意味着创建 CLR 编译器的扩展,老实说我不知道它是如何完成的。
最后的选择是使用 国际奥委会框架. 。也许这不是完美的解决方案,因为大多数 IoC 框架通过定义允许挂钩方法的入口点来工作,但是,根据您想要实现的目标,这可能是一个公平的近似值。
其他提示
如果您编写一个类 - 称之为 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 ...]
}
}
}
你可以通过以下方式实现它 拦截 DI 容器的功能,例如 温莎城堡. 。事实上,可以以这样的方式配置容器,即每个具有由特定属性修饰的方法的类都将被拦截。
关于第 3 点,OP 要求一个没有 AOP 框架的解决方案。我在下面的回答中假设应该避免的是 Aspect、JointPoint、PointCut 等。根据 CastleWindsor 的拦截文档, ,这些都不需要完成所要求的任务。
根据属性的存在配置拦截器的通用注册:
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;
}
}
将创建的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");
}
}
将日志记录属性添加到您的方法中以记录日志
public class Traced
{
[Log]
public void Method1(String name, Int32 value) { }
[Log]
public void Method2(Object object) { }
}
请注意,如果只需要拦截类的某些方法,则需要对属性进行一些处理。默认情况下,所有公共方法都会被拦截。
看看这个 - 相当重的东西..http://msdn.microsoft.com/en-us/magazine/cc164165.aspx
Essential .net - don box 有一个章节介绍了您需要的内容,称为“拦截”。我在这里刮了一些(抱歉字体颜色 - 我当时有一个黑暗的主题......)http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html
我找到了一种不同的方法,可能更容易......
声明一个方法 InvokeMethod
[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;
}
现在我可以在运行时进行检查而无需依赖注入......
网站上没有陷阱:)
希望您会同意,这比 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();
}
}
如果你想不受限制地跟踪你的方法(没有代码适配,没有AOP框架,没有重复代码),让我告诉你,你需要一些魔法......
说真的,我解决了它来实现一个在运行时工作的 AOP 框架。
您可以在这里找到: NConcern .NET AOP 框架
我决定创建这个AOP框架来响应这种需求。它是一个非常轻量级的简单库。您可以在主页中看到记录器的示例。
如果您不想使用第3方程序集,可以浏览代码源(开源)并复制这两个文件 Aspect.Directory.cs 和 Aspect.Directory.Entry.cs 根据您的意愿进行调整。这些类允许在运行时替换您的方法。我只是要求您尊重许可证。
我希望您能找到您需要的东西或说服您最终使用 AOP 框架。
您可以使用开源框架 C注入 在 CodePlex 上。您可以编写最少的代码来创建注入器,并让它使用 CInject 快速拦截任何代码。另外,由于这是开源的,您也可以扩展它。
或者您可以按照本文中提到的步骤进行操作 使用 IL 拦截方法调用 并使用 C# 中的 Reflection.Emit 类创建您自己的拦截器。
我不知道解决方案,但我的方法如下。
使用自定义属性来装饰类(或其方法)。在程序的其他地方,让初始化函数反映所有类型,读取用属性修饰的方法,并将一些 IL 代码注入到该方法中。实际上可能更实用 代替 通过存根调用的方法 LogStart
, ,实际方法然后 LogEnd
. 。此外,我不知道您是否可以使用反射更改方法,因此替换整个类型可能更实用。
您可能会使用 GOF 装饰器模式,并“装饰”所有需要跟踪的类。
它可能只对 IOC 容器真正实用(但正如前面指出的,如果您要走 IOC 路径,您可能需要考虑方法拦截)。
你需要向 Ayende 询问他是如何做到的:http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx
AOP 是干净代码实现的必备条件,但是如果您想在 C# 中包围一个块,泛型方法的使用相对更容易。(具有智能感知和强类型代码)当然,它不能成为 AOP 的替代品。
虽然 后竖琴 有一些小问题(我对在生产中使用没有信心),这是一个好东西。
通用包装类,
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");
- 编写您自己的 AOP 库。
- 使用反射在您的实例上生成日志记录代理(不确定是否可以在不更改现有代码的某些部分的情况下完成此操作)。
- 重写程序集并注入日志代码(基本上与 1 相同)。
- 托管 CLR 并在此级别添加日志记录(我认为这是最难实现的解决方案,但不确定 CLR 中是否有所需的挂钩)。
在 C# 6 发布“nameof”之前,您能做的最好的事情就是使用缓慢的 StackTrace 和 linq 表达式。
例如。对于这样的方法
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
}