英文:
Dynamically call a specific method on every other method call
问题
我在.NET 4.8下使用Windows Forms。我在项目中声明了特定数量的方法,分布在不同的类中。我希望这些类中的每个方法在执行时都调用另一个特定的方法。我的意思是,如果我在每个方法体的第一行手动添加所需的代码来调用这个特定的方法。
为什么我想这样做呢?我写了一个特定的方法,用于打印我感兴趣的调试信息。这个方法包括有关调用者成员的信息,但并不限于此类信息。
然后,在调试模式下开发任何应用程序时,我希望在项目中的每个其他方法调用时自动调用这个方法(如果可能的话,也包括属性的getter/setter),我只是很好奇是否存在一种替代方法,可以避免在项目的每个方法体中写/复制一千次调用这个特定方法的指令的噩梦。
是否存在在运行时实现这一目标的方法?也许...编写一个帮助方法,它会被调用一次以通过反射检索所有声明的方法,并在它们的方法体中进行代码注入点的操作?不确定如何做到这一点,也不确定这是否可行;也许可以像这个答案中建议的那样使用Reflection.Emit?
而且,是否可以在不依赖第三方依赖项如Postsharp的情况下实现这一目标,正如这个问题中建议的那样?实际上,对我来说,使用Postsharp并不是一个可行的解决方案,因为我认为解决方案将包括在项目中的每个声明的方法上附上自定义属性。
我还找到了另一个建议,但基本上就是我想要避免的事情:在项目中的每个方法调用中手动添加代码更改(在这种情况下,用于替换调用代码)。
像我找到的最后一个建议那样的一些建议并不适用于我的情况,因为我需要在其他方法的方法体内/从中调用这个特定方法,以便能够检索当前调用者成员的调试信息,包括其参数和值。
英文:
I'm on .NET 4.8, under Windows Forms. I have a specific amount of declared methods inside a project, in various classes. And I would like that every method in these classes to call another specific method at the very start of each method execution. What I mean it's like If I manually add the required code at line number one inside each method body to call this other specific method.
Why I would like to do this?: I wrote a specific method that prints debug information on which I'm interested to know. This method includes info about the caller member, but it is not in any way limited to that kind of info.
Then, while I'm developing any application in Debug mode I would like to call this method automatically on every other method call in my project (inside property getter/setter too, if possible), and I'm just very curious to know if exists an alternative to the nightmare of writing/copying a thousand times the required instruction to call this specific method inside each method body in the project.
Exists a way to achieve this at runtime?. Maybe... writing an helper method that would be called once to retrieve all declared methods through Reflection and do code injection points in their method bodies?. Not sure how to do so, and not sure if that is viable anyways; maybe with Reflection.Emit as suggested in this answer?
And could this be achieved without depending on third party dependencies like Postsharp as suggested in this question?. Really using Postsharp is not a viable solution for me because I suppose the solution will consist to decorate with custom attributes every declared method in the project.
I also found this other suggestion but it's basically what I intend to avoid: manually add code changes to every method call in the project (in this case to replace the invocation code).
Some suggestions that I found like that last are not applicable to my scenario because I need to call this specific method INSIDE / FROM the method body of other methods in order to be able retrieve debug info of the current caller member including its parameters and values.
答案1
得分: 2
以下是您提供的内容的翻译:
作为一种选择,RealProxy 可能会有所帮助。Bruno Sonnino在这个主题上有一篇很好的文章:面向方面的编程:使用RealProxy类的面向方面的编程。
此外,您还可以尝试使用Unity(依赖注入框架)的AOP功能来实现这个目的。Dino Esposito在这个主题上有一篇很好的文章:Unity中的拦截器。
使用像Fody这样的织入工具也是另一种选择。
使用Reflection.Emit创建代理类也是另一种选择。
示例 - 使用RealProxy进行AOP
具有MethodExecuting和MethodExecuted的LogAttribute,分别在方法之前和之后运行
使用RealProxy,您可以为您的类创建代理,以便在调用方法时,代理的Invoke方法将运行,您可以在那里运行任何逻辑,例如您可以在实际方法调用之前或之后运行一些内容。
在此示例中,我展示了如何创建一个MethodFilterAttribute
,其中有两个方法OnMethodExecuting
和OnMethodExecuted
,如果您用继承自此属性的属性装饰您的方法,那么这些方法将在原始方法执行之前和之后运行。
从代码中可以看出,您不一定需要属性,属性只是作为一个扩展点存在。
在此示例中,代码的用法类似于:
var calc = CalculatorFactory.GetInstance();
var a = calc.Add(1, 2);
var b = calc.Subtract(1, 2);
这将产生以下输出:
Add执行。
Add已执行。
Subtract执行。
Subtract已执行。
Using语句
这是一个可用于方法的属性。它具有OnMethodExecuting和OnMethodExecuted方法,当您获取类的代理并运行方法时,这两个过滤器方法将在使用此属性装饰的方法之前和之后执行:
using System;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Linq;
[AttributeUsage(AttributeTargets.Method)]
public class MethodFilterAttribute : Attribute
{
public int Order { get; set; }
public virtual void OnMethodExecuting(
MethodInfo methodInfo, object[] args) { }
public virtual void OnMethodExecuted(
MethodInfo methodInfo, object[] args, object result) { }
}
LogAttribute
MethodFilterAttribute的实现,用于在方法执行之前和之后执行日志:
public class LogAttribute : MethodFilterAttribute
{
override public void OnMethodExecuting(
MethodInfo methodInfo, object[] args)
{
Console.WriteLine($"{methodInfo.Name}执行中。");
}
override public void OnMethodExecuted(
MethodInfo methodInfo, object[] args, object result)
{
Console.WriteLine($"{methodInfo.Name}已执行。");
}
}
DynamicProxy类
创建您的对象的代理,如果运行对象的方法,如果该方法被方法过滤器属性装饰,那么将运行OnActionExecuting和OnActionExecuted。
从代码中可以看出,您不一定需要属性,属性只是作为一个扩展点存在。
public class DynamicProxy<T> : RealProxy
{
private readonly T original;
public DynamicProxy(T original)
: base(typeof(T))
{
this.original = original;
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
try
{
var filters = methodInfo.GetCustomAttributes<MethodFilterAttribute>();
if (filters.Any())
{
filters.OrderBy(x => x.Order).ToList()
.ForEach(f => f.OnMethodExecuting(methodInfo, methodCall.InArgs));
}
var result = methodInfo.Invoke(original, methodCall.InArgs);
if (filters.Any())
{
filters.OrderBy(x => x.Order).ToList()
.ForEach(f => f.OnMethodExecuted(methodInfo, methodCall.InArgs, result));
}
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
return new ReturnMessage(e, methodCall);
}
}
}
ICalculator接口和Calculator类
Calculator的方法都用Log属性装饰,这意味着在执行这些方法之前和之后,将运行日志。
请注意:在代理的实现中,我们使用接口查找属性。同时,拥有接口是必要的。
public interface ICalculator
{
[Log]
int Add(int x, int y);
[Log]
int Subtract(int x, int y);
}
public class Calculator : ICalculator
{
public int Add(int x, int y)
{
return x + y;
}
public int Subtract(int x, int y)
{
return x - y;
}
}
CalculatorFactory
返回ICalculator的代理实例的工厂:
public class CalculatorFactory
{
public static ICalculator GetInstance()
{
var original = new Calculator();
return new DynamicProxy<ICalculator>(original)
.GetTransparentProxy() as ICalculator;
}
}
用法
使用工厂获取接口的代理实例并运行方法:
var calc = CalculatorFactory.GetInstance();
var a = calc.Add(1, 2);
<details>
<summary>英文:</summary>
As an option, [RealProxy](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.remoting.proxies.realproxy?view=netframework-4.8.1&WT.mc_id=DT-MVP-5003235) may help. There's a nice article by Bruno Sonnino on this topic: [Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class](https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/february/aspect-oriented-programming-aspect-oriented-programming-with-the-realproxy-class?WT.mc_id=DT-MVP-5003235).
Also you may be able to use AOP features of [Unity](https://learn.microsoft.com/en-us/previous-versions/msp-n-p/dn178466(v=pandp.30)?redirectedfrom=MSDN) (the DI framework) for this purpose. There's a nice article on this topic by Dino Esposito: [Interceptors in Unity](https://chainding.wordpress.com/2012/04/25/interceptors-in-unity/).
Using a weaving tool like [Fody](https://github.com/Fody/Fody) is also another option.
Creating proxy classes using [Reflection.Emit](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit?view=net-7.0&WT.mc_id=DT-MVP-5003235) could be another option.
### Example - AOP using RealProxy
***A LogAttribute which Has MethodExecuting and MethodExecuted and runs before and after methods***
Using RealProxy, you can create a proxy for your class, so that when you call a method, the `Invoke` method of the proxy will run and you can run any logic there, for example you can run something before or after the actual method call.
In this example, I show how you can create a `MethodFilterAttribute` having two methods `OnMethodExecuting` and `OnMethodExecuted`, and then if you decorate your method with an attribute derived from this attribute, those methods will run before and after executing of the original method.
Looking into the code, you see you don't necessarily need the attributes, and attributes are just there as an extensibility point.
The usage of the code in this example is something like this:
var calc = CalculatorFactory.GetInstance();
var a = calc.Add(1, 2);
var b = calc.Subtract(1, 2);
Which produce the output:
Add executing.
Add executed.
Subtract executing.
Subtract executed.
**Using statements**
This is an attribute that could be used for methods. It has OnMethodExecuting and OnMethodExecuted methods, and when you get a proxy of your class, and run the methods, these two filter methods will be executed before and after the method which is decorated by this attribute:
using System;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Linq;
[AttributeUsage(AttributeTargets.Method)]
public class MethodFilterAttribute : Attribute
{
public int Order { get; set; }
public virtual void OnMethodExecuting(
MethodInfo methodInfo, object[] args) { }
public virtual void OnMethodExecuted(
MethodInfo methodInfo, object[] args, object result) { }
}
**LogAttribute**
An implementation of MethodFilterAttribute which performs log before and after method execution:
public class LogAttribute : MethodFilterAttribute
{
override public void OnMethodExecuting(
MethodInfo methodInfo, object[] args)
{
Console.WriteLine($"{methodInfo.Name} executing.");
}
override public void OnMethodExecuted(
MethodInfo methodInfo, object[] args, object result)
{
Console.WriteLine($"{methodInfo.Name} executed.");
}
}
**The DynamicProxy class**
Creates a proxy of your object, and if you run methods of the object, if the methdod is decorated with a method filter attribute, then OnActionExecuting and OnActionExecuted will run.
Looking into the code, you see you don't necessarily need the attributes, and attributes are just there as an extensibility point.
public class DynamicProxy<T> : RealProxy
{
private readonly T original;
public DynamicProxy(T original)
: base(typeof(T))
{
this.original = original;
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
try
{
var filters = methodInfo.GetCustomAttributes<MethodFilterAttribute>();
if (filters.Any())
{
filters.OrderBy(x => x.Order).ToList()
.ForEach(f => f.OnMethodExecuting(methodInfo, methodCall.InArgs));
}
var result = methodInfo.Invoke(original, methodCall.InArgs);
if (filters.Any())
{
filters.OrderBy(x => x.Order).ToList()
.ForEach(f => f.OnMethodExecuted(methodInfo, methodCall.InArgs, result));
}
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
return new ReturnMessage(e, methodCall);
}
}
}
**ICalculator interface and Calculator class**
Methods of Calculator are decorated with Log attribute, which means before and after execution of those methods, log will run.
Please note: In the implementation of the proxy we are looking for the attributes, using the interface. Also having the interface is necessary.
public interface ICalculator
{
[Log]
int Add(int x, int y);
[Log]
int Subtract(int x, int y);
}
public class Calculator : ICalculator
{
public int Add(int x, int y)
{
return x + y;
}
public int Subtract(int x, int y)
{
return x - y;
}
}
**CalculatorFactory**
The factory which returns a proxy instance of ICalculator:
public class CalculatorFactory
{
public static ICalculator GetInstance()
{
var original = new Calculator();
return new DynamicProxy<ICalculator>(original)
.GetTransparentProxy() as ICalculator;
}
}
**Usage**
Get an proxied instance of the interface using the factory and run methods:
var calc = CalculatorFactory.GetInstance();
var a = calc.Add(1, 2);
var b = calc.Subtract(1, 2);
Which produce the output:
Add executing.
Add executed.
Subtract executing.
Subtract executed.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论