对于给定的类,我希望有跟踪功能,即我想记录每个方法调用(方法签名和实际参数值)和每个方法退出(只是方法签名)。
我如何做到这一点,假设:
为了使问题更具体,让我们假设有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);
}
如何在每次调用 Method1 和 Method2 时调用 Logger.LogStart 和 Logger.LogEnd 修改 Caller.Call 方法,而不是将调用显式添加到 Traced.Method1 和 Traced.Method2 ?
编辑:如果我允许稍微改变Call方法,会有什么解决方案?
答案 0 :(得分:66)
C#不是面向AOP的语言。它有一些AOP功能,你可以模仿其他一些,但用C#制作AOP很痛苦。
我想方设法做你想做的事情,我发现没有简单的办法。
据我了解,这就是你想要做的事情:
[Log()]
public void Method1(String name, Int32 value);
为了做到这一点,你有两个主要选择
从MarshalByRefObject或ContextBoundObject继承您的类,并定义一个继承自IMessageSink的属性。 This article有一个很好的例子。你必须考虑使用MarshalByRefObject,性能会像地狱一样下降,我的意思是,我说的是性能损失了10倍,所以在尝试之前要仔细考虑。
另一种选择是直接注入代码。在运行时,意味着你将不得不使用反射“读取”每个类,获取其属性并注入适当的调用(为此我认为你不能使用Reflection.Emit方法,因为我认为Reflection.Emit不会不允许您在现有方法中插入新代码。在设计时,这将意味着创建一个CLR编译器的扩展,我真的不知道它是如何完成的。
最后一个选项是使用IoC framework。也许它不是完美的解决方案,因为大多数IoC框架通过定义允许方法被挂钩的入口点来工作,但是,根据你想要实现的目标,这可能是一个公平的近似。
答案 1 :(得分:45)
实现这一目标的最简单方法可能是使用PostSharp。它根据您应用于它的属性在方法中注入代码。它可以让你完全按照自己的意愿行事。
另一个选择是使用profiling API在方法中注入代码,但这确实是硬核。
答案 2 :(得分:10)
如果你编写一个类 - 称之为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 ...]
}
}
}
答案 3 :(得分:5)
您可以使用Interception等DI容器的Castle Windsor功能实现此功能。实际上,可以以这样的方式配置容器:每个具有由特定属性修饰的方法的类都将被截获。
关于第3点,OP要求提供没有AOP框架的解决方案。我在下面的回答中假设应该避免的是Aspect,JointPoint,PointCut等。根据Interception documentation from 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;
}
}
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) { }
}
请注意,如果只需要拦截某个类的某些方法,则需要对该属性进行一些处理。默认情况下,将拦截所有公共方法。
答案 4 :(得分:4)
看看这个 - 相当重的东西.. http://msdn.microsoft.com/en-us/magazine/cc164165.aspx
Essential .net - don box有一个章节,你需要什么称为拦截。 我在这里刮了一些(对不起字体的颜色 - 我当时有一个黑暗的主题...) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html
答案 5 :(得分:4)
我找到了一种可能更容易的不同方式......
声明一个方法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或使用远程处理或代理类派生的重量。
答案 6 :(得分:4)
首先,你必须修改你的类来实现一个接口(而不是实现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();
}
}
答案 7 :(得分:4)
如果你想追踪你的方法而没有限制(没有代码改编,没有AOP框架,没有重复的代码),让我告诉你,你需要一些魔法......
说真的,我解决了它在运行时实现AOP框架的问题。
您可以在此处找到:NConcern .NET AOP Framework
我决定创建这个AOP框架来回应这种需求。它是一个非常轻量级的简单库。您可以在主页中看到记录器的示例。
如果您不想使用第三方程序集,可以浏览代码源(开源)并复制文件Aspect.Directory.cs和Aspect.Directory.Entry.cs以根据您的意愿进行调整。这些类允许在运行时替换您的方法。我只想请你尊重许可证。
我希望你能找到你需要的东西或说服你最终使用AOP框架。
答案 8 :(得分:2)
您可以在CodePlex上使用开源框架CInject。您可以编写最少的代码来创建Injector,并使用CInject快速拦截任何代码。另外,由于这是开源,您也可以扩展它。
或者您可以按照Intercepting Method Calls using IL上本文中提到的步骤操作,并使用C#中的Reflection.Emit类创建自己的拦截器。
答案 9 :(得分:1)
我不知道解决方案,但我的方法如下。
使用自定义属性装饰类(或其方法)。在程序的其他地方,让初始化函数反映所有类型,读取用属性修饰的方法并将一些IL代码注入到方法中。通过调用LogStart
的存根(实际方法然后LogEnd
替换方法实际上可能更实际。另外,我不知道你是否可以使用反射来改变方法,因此替换整个类型可能更实际。
答案 10 :(得分:1)
您可以使用GOF装饰器模式,并“装饰”所有需要跟踪的类。
它可能只对IOC容器非常实用(但是如果你要沿着IOC路径走下去,你可能想要考虑方法拦截。)
答案 11 :(得分:1)
你需要告诉Ayende他是如何做到这一点的答案: http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx
答案 12 :(得分:1)
AOP是实现干净代码的必备条件,但是如果要在C#中包围块,则通用方法的使用相对容易。 (具有智能和强类型代码)当然,它不能替代AOP。
虽然PostSHarp几乎没有错误问题(我对在制作中使用感到不自信),但这是一件好事。
通用包装类
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");
答案 13 :(得分:0)
也许这个答案来的太晚了,但是到了。
您要实现的目标内置在MediatR库中。
这是我的RequestLoggerBehaviour,它拦截对我的业务层的所有调用。
namespace SmartWay.Application.Behaviours
{
public class RequestLoggerBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger _logger;
private readonly IAppSession _appSession;
private readonly ICreateLogGrain _createLogGrain;
public RequestLoggerBehaviour(ILogger<TRequest> logger, IAppSession appSession, IClusterClient clusterClient)
{
_logger = logger;
_appSession = appSession;
_createLogGrain = clusterClient.GetGrain<ICreateLogGrain>(Guid.NewGuid());
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var name = typeof(TRequest).Name;
_logger.LogInformation($"SmartWay request started: ClientId: {_appSession.ClientId} UserId: {_appSession.UserId} Operation: {name} Request: {request}");
var response = await next();
_logger.LogInformation($"SmartWay request ended: ClientId: {_appSession.ClientId} UserId: {_appSession.UserId} Operation: {name} Request: {request}");
return response;
}
}
}
例如,您还可以创建性能行为来跟踪执行时间太长的方法。
在业务层上具有干净的架构(MediatR)将使您在执行SOLID原则时保持代码干净。
您可以在此处查看其工作方式: https://youtu.be/5OtUm1BLmG0?t=1
答案 14 :(得分:-1)
答案 15 :(得分:-3)
在C#6之前你可以做的最好的事情是&#39; nameof&#39;发布的是使用缓慢的StackTrace和linq表达式。
E.g。对于这种方法
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
}