如何在 C # 中拦截方法调用?

对于给定的类,我希望具有跟踪功能,例如,我希望记录每个方法调用(方法签名和实际参数值)和每个方法出口(只记录方法签名)。

如果假设:

  • 我不想用任何第三方 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);
}

如何在每次调用 方法1方法2时调用 记录器,记录开始日志记录器,日志终端,而不修改 有人打电话,有人打电话方法,也不显式地将调用添加到 追踪,方法1追踪,方法2

编辑: 如果允许我稍微改变 Call 方法,解决方案是什么?

103691 次浏览
  1. 编写您自己的 AOP 库。
  2. 使用反射在实例上生成日志代理(不确定是否可以在不更改部分现有代码的情况下完成)。
  3. 重写程序集并注入日志代码(基本上与1相同)。
  4. 承载 CLR 并在这个级别添加日志记录(我认为这是最难实现的解决方案,但不确定 CLR 中是否有所需的挂钩)。

我不知道一个解决方案,但我的方法是这样的。

用自定义属性装饰类(或其方法)。在程序的其他地方,让初始化函数反映所有类型,读取用属性修饰的方法,并向方法中注入一些 IL 代码。通过存根调用 LogStart,实际的方法,然后调用 LogEnd,对于 更换来说,实际上可能更实际一些。此外,我不知道是否可以使用反射来更改方法,因此替换整个类型可能更实际。

如果编写一个实现 IDisposable 接口的类(称为 Traces) ,则可以将所有方法体包装在

Using( Tracing tracing = new Tracing() ){ ... method body ...}

在 Trace 类中,可以分别在构造函数/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-don 框中有一章介绍了你需要的叫做拦截的东西。 我刮了一些在这里(对不起字体颜色-我有一个黑暗的主题回来了...) Http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html

C # 不是面向 AOP 的语言。它有一些 AOP 特性,您可以模拟其他一些特性,但是使用 C # 制作 AOP 是痛苦的。

我寻找各种方法来做你想做的事但我发现没有简单的方法。

据我所知,你想这么做:

[Log()]
public void Method1(String name, Int32 value);

为了做到这一点,你有两个主要的选择

  1. 从 MarshalByRefObject 或 Contextbound Object 继承您的类,并定义从 IMessageSink 继承的属性。这篇文章就是一个很好的例子。尽管如此,您还是必须考虑使用 MarshalByRefObject 会导致性能下降,我是认真的,我说的是10倍的性能损失,所以在尝试之前请仔细考虑。

  2. 另一种选择是直接注入代码。在运行时,这意味着您必须使用反射来“读取”每个类,获取它的属性并注入适当的调用(就此而言,我认为您不能使用反射。发出方法,因为我认为反射。Emit 不允许您在已存在的方法中插入新代码)。在设计时,这将意味着创建一个扩展到 CLR 编译器,老实说,我不知道它是如何完成的。

最后一个选项是使用 IoC 框架。也许这不是一个完美的解决方案,因为大多数 IoC 框架都是通过定义允许方法连接的入口点来工作的,但是,根据您想要实现的目标,这可能是一个公平的近似解决方案。

实现这一点的最简单方法可能是使用 PostSharp。它根据应用于它的属性在方法中注入代码。它允许你做你想做的事。

另一种选择是使用 分析空气污染指数在方法内部注入代码,但这确实是非常困难的。

你可以使用 gOF 修饰模式,“装饰”所有需要跟踪的类。

它可能只适用于 IOC 容器(但是作为前面的指针,如果要沿着 IOC 路径前进,可能需要考虑方法拦截)。

我已经找到了一种不同的方法,可能更容易..。

声明一个方法调用方法

[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 派生或者使用远程处理或代理类要轻。

AOP 是实现干净代码所必须的,但是如果你想在 C # 中包围一个块,泛型方法有相对容易的使用。(具有智能感和强类型代码)当然,它不能作为 AOP 的替代品。

虽然 调整后有一些 bug 问题(我对在生产中使用没有信心) ,但它是一个很好的东西。

泛型包装类,

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 上使用开源框架 注射。您可以编写最少的代码来创建一个 Injector,并让它使用 CInject 快速拦截任何代码。另外,因为这是开放源码,你也可以扩展它。

或者您可以按照本文中提到的步骤在 使用 IL 拦截方法调用上使用反射创建您自己的拦截器。在 C # 中发出类。

在 C # 6发布‘ nameof’之前,您所能做的最好的事情就是使用缓慢的 StackTrace 和 linq Expressions。

例如这种方法

    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
}

首先,您必须修改您的类来实现一个接口(而不是实现 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();
}
}

您可以使用 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;
}
}

向容器中添加创建的 IContributeComponent entModelConstruction

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) { }
}

注意,如果只需要截获类的某个方法,则需要对属性进行某些处理。默认情况下,所有公共方法都将被拦截。

如果你想不受限制地跟踪你的方法(没有代码调整,没有 AOP 框架,没有重复的代码) ,让我告诉你,你需要一些魔法..。

说真的,我解决了这个问题,实现了一个在运行时工作的 AOP 框架。

你可以在这里找到: NETAOP 框架

我决定创建这个 AOP 框架来响应这种需求。它是一个非常轻量级的简单库。您可以在主页中看到日志记录器的示例。

如果不想使用第三方程序集,可以浏览代码源(开放源代码)并根据需要复制 方面。目录。 c方面,目录,入口文件。这些类允许在运行时替换方法。我只是希望你能尊重执照。

我希望您能找到您需要的东西,或者说服您最终使用 AOP 框架。

也许现在回答这个问题已经太迟了,但是我要说的是。

您希望实现的目标是在 MediatR 库中构建的。

这是我的 RequestLogger 行为,它拦截对我的业务层的所有调用。

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