[英]How do I intercept a method call in C#?
對於給定的類,我希望具有跟蹤功能,即我希望記錄每個方法調用(方法簽名和實際參數值)和每個方法退出(僅方法簽名)。
假設我如何做到這一點:
為了使問題更具體,讓我們假設有 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);
}
我如何調用Logger.LogStart和Logger.LogEnd每次調用方法1和方法2,而無需修改Caller.Call方法,沒有明確地加入調用Traced.Method1和Traced.Method2?
編輯:如果允許我稍微更改 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 框架。 也許這不是完美的解決方案,因為大多數 IoC 框架通過定義允許方法被掛鈎的入口點來工作,但是,根據您想要實現的目標,這可能是一個公平的近似值。
您可以使用 DI 容器(例如Castle Windsor )的攔截功能來實現它。 實際上,可以以這樣的方式配置容器,即攔截每個具有由特定屬性修飾的方法的類。
關於第 3 點,OP 要求沒有 AOP 框架的解決方案。 我在以下答案中假設應該避免的是 Aspect、JointPoint、PointCut 等。根據CastleWindsor 的 Interception 文檔,這些都不需要完成所要求的內容。
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) { }
}
請注意,如果只需要攔截某個類的某些方法,則需要對該屬性進行一些處理。 默認情況下,所有公共方法都會被攔截。
如果您編寫一個實現 IDisposable 接口的類(稱為 Tracing),則可以將所有方法體包裝在一個
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 ...]
}
}
}
如果你想無限制地追蹤你的方法(沒有代碼改編,沒有 AOP 框架,沒有重復的代碼),讓我告訴你,你需要一些魔法......
說真的,我解決了它以實現在運行時工作的 AOP 框架。
您可以在這里找到: NConcern .NET AOP 框架
我決定創建這個 AOP 框架來響應這種需求。 它是一個非常輕量級的簡單庫。 您可以在主頁中看到記錄器的示例。
如果您不想使用第 3 方程序集,您可以瀏覽代碼源(開源)並復制兩個文件Aspect.Directory.cs和Aspect.Directory.Entry.cs以根據您的意願進行調整。 這些類允許在運行時替換您的方法。 我只是要求你尊重許可證。
我希望你能找到你需要的東西或說服你最終使用 AOP 框架。
看看這個 - 相當沉重的東西.. http://msdn.microsoft.com/en-us/magazine/cc164165.aspx
Essential .net - don box 有一章介紹了您需要的內容,稱為攔截。 我在這里刮了一些(對不起字體顏色 - 當時我有一個黑暗的主題......) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html
首先,您必須修改您的類以實現一個接口(而不是實現 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();
}
}
我找到了一種可能更容易的不同方法......
聲明一個方法 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 派生或使用遠程處理或代理類更輕。
您可以在 CodePlex 上使用開源框架CInject 。 您可以編寫最少的代碼來創建一個注入器,並使用 CInject 讓它快速攔截任何代碼。 另外,由於這是開源的,您也可以擴展它。
或者,您可以按照使用 IL 攔截方法調用的這篇文章中提到的步驟操作,並使用 C# 中的 Reflection.Emit 類創建您自己的攔截器。
你需要給 Ayende 找一個關於他是如何做到的答案: http : //ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx
我不知道解決方案,但我的方法如下。
使用自定義屬性裝飾類(或其方法)。 在程序的其他地方,讓一個初始化函數反映所有類型,讀取用屬性修飾的方法並將一些 IL 代碼注入到方法中。 將方法替換為調用LogStart
的存根、實際方法然后是LogEnd
可能更實用。 此外,我不知道您是否可以使用反射更改方法,因此替換整個類型可能更實用。
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");
您可以潛在地使用 GOF 裝飾器模式,並“裝飾”所有需要跟蹤的類。
它可能只適用於 IOC 容器(但作為早先指出的指針,如果您要沿着 IOC 路徑走下去,您可能需要考慮方法攔截)。
也許這個答案為時已晚,但它就在這里。
您希望實現的目標是內置於 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
在 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
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.