C#:如何在调用时触发事件的方法上创建属性?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/226420/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 18:57:07  来源:igfitidea点击:

C#: How to create an attribute on a method triggering an event when it is invoked?

c#.neteventsmethodsattributes

提问by Tamas Czinege

Is there a way in C# or .NET in general to create an attribute on a method which triggers an event when the method is invoked? Ideally, I would be able to run custom actions before and after the invocation of the method.

通常在 C# 或 .NET 中是否有一种方法可以在调用方法时触发事件的方法上创建属性?理想情况下,我将能够在调用该方法之前和之后运行自定义操作。

I mean something like this:

我的意思是这样的:

[TriggersMyCustomAction()]
public void DoSomeStuff()
{
}

I am totally clueless how to do it or if it possible at all, but System.Diagnostic.ConditionalAttributemight do a similar thing in the background. I am not sure though.

我完全不知道如何做或者如果可能的话,但System.Diagnostic.ConditionalAttribute可能会在后台做类似的事情。我不确定。

EDIT: I forgot to mention that due to the circumstances of my specific case, performance is not really an issue.

编辑:我忘了提到,由于我的具体情况,性能并不是真正的问题。

采纳答案by OwenP

The only way I know how to do this is with PostSharp. It post-processes your IL and can do things like what you asked for.

我知道如何做到这一点的唯一方法是使用PostSharp。它对您的 IL 进行后处理,并且可以执行您要求的操作。

回答by wprl

I don't think there is a way to do it with just an attribute, but using proxy classesand reflection you could have a class that knows to intercept instantiations of the classes in which you have attributed methods.

我不认为有一种方法可以只用一个属性来做到这一点,但是使用代理类和反射,你可以有一个知道拦截你有属性方法的类的实例的类。

Then the proxy class can trigger an event whenever the attributed methods are called.

然后代理类可以在调用属性方法时触发事件。

回答by Nick

An attribute gives information, they are metadata. I don't know of a way to do this offhand, someone might.

属性提供信息,它们是元数据。我不知道有什么办法可以立即做到这一点,有人可能会这样做。

You could look at partial methods in .NET which allow you to do some lightweight event handling. You provide the hooks and let someone else handle the implementation. If the method isn't implemented the compiler just ignores it.

您可以查看 .NET 中的部分方法,它允许您执行一些轻量级事件处理。您提供钩子并让其他人处理实现。如果该方法没有实现,编译器就会忽略它。

http://msdn.microsoft.com/en-us/library/wa80x488.aspx

http://msdn.microsoft.com/en-us/library/wa80x488.aspx

回答by Ben Scheirman

You need some sort of Aspect oriented framework. PostSharp will do it, as will Windsor.

您需要某种面向方面的框架。PostSharp 会这样做,Windsor也会这样做。

Basically, they subclass your object and override this method...

基本上,他们将您的对象子类化并覆盖此方法......

then it becomes:

然后它变成:

//proxy
public override void DoSomeStuff()
{
     if(MethodHasTriggerAttribute)
        Trigger();

     _innerClass.DoSomeStuff();
}

of course all this is hidden to you. All you have to do is ask Windsor for the type, and it will do the proxying for you. The attribute becomes a (custom) facility I think in Windsor.

当然,这一切对你来说都是隐藏的。你所要做的就是向 Windsor 询问类型,它会为你做代理。该属性成为我认为在温莎的(自定义)设施。

回答by Hallgrim

You can use ContextBoundObject and IMessageSink. See http://msdn.microsoft.com/nb-no/magazine/cc301356(en-us).aspx

您可以使用 ContextBoundObject 和 IMessageSink。请参阅http://msdn.microsoft.com/nb-no/magazine/cc301356(en-us).aspx

Be warned that this approach has a severe performance impact compared with a direct method call.

请注意,与直接方法调用相比,此方法会严重影响性能。

回答by Matt

This concept is used in MVCweb applications.

这个概念用于MVCWeb 应用程序。

The .NET Framework 4.xprovides several attributes which trigger actions, e.g.: ExceptionFilterAttribute(handling exceptions), AuthorizeAttribute(handling authorization). Both are defined in System.Web.Http.Filters.

.NET框架4.x版提供了几种属性,其触发动作,例如:ExceptionFilterAttribute(异常处理), AuthorizeAttribute(处理授权)。两者都在 中定义System.Web.Http.Filters

You could for instance define your own authorization attribute as follows:

例如,您可以定义自己的授权属性,如下所示:

public class myAuthorizationAttribute : AuthorizeAttribute
{
    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        // do any stuff here
        // it will be invoked when the decorated method is called
        if (CheckAuthorization(actionContext)) 
           return true; // authorized
        else
           return false; // not authorized
    }

}

Then, in your controllerclass you decorate the methods which are supposed to use your authorization as follows:

然后,在您的控制器类中,您装饰应该使用您的授权的方法,如下所示:

[myAuthorization]
public HttpResponseMessage Post(string id)
{
    // ... your code goes here
    response = new HttpResponseMessage(HttpStatusCode.OK); // return OK status
    return response;
}

Whenever the Postmethod is invoked, it will call the IsAuthorizedmethod inside the myAuthorizationAttribute beforethe code inside the Postmethod is executed.

每当Post调用该方法时,它都会在执行该IsAuthorized方法内部的代码之前调用该myAuthorizationAttribute内部的方法。Post

If you return falsein the IsAuthorizedmethod, you signal that authorization is not granted and the execution of the method Postaborts.

如果falseIsAuthorized方法中返回,则表示未授予授权并且方法的执行Post中止。



To understand how this works, let's look into a different example: The ExceptionFilter, which allows filtering exceptions by using attributes, the usage is similar as shown above for the AuthorizeAttribute(you can find a more detailed description about its usage here).

为了理解它是如何工作的,让我们看一个不同的例子:ExceptionFilter,它允许使用属性过滤异常,用法与上面所示的类似AuthorizeAttribute(您可以在此处找到有关其用法的更详细说明)。

To use it, derive the DivideByZeroExceptionFilterclass from the ExceptionFilterAttributeas shown here, and override the method OnException:

要使用它,请DivideByZeroExceptionFilter此处ExceptionFilterAttribute所示派生类,并覆盖方法:OnException

public class DivideByZeroExceptionFilter : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    {
        if (actionExecutedContext.Exception is DivideByZeroException)
        {
            actionExecutedContext.Response = new HttpResponseMessage() { 
                Content = new StringContent("A DIV error occured within the application.",
                                System.Text.Encoding.UTF8, "text/plain"), 
                StatusCode = System.Net.HttpStatusCode.InternalServerError
                };
        }
    }
}

Then use the following demo code to trigger it:

然后使用下面的演示代码来触发它:

[DivideByZeroExceptionFilter]
public void Delete(int id)
{
    // Just for demonstration purpose, it
    // causes the DivideByZeroExceptionFilter attribute to be triggered:
    throw new DivideByZeroException(); 

    // (normally, you would have some code here that might throw 
    // this exception if something goes wrong, and you want to make
    // sure it aborts properly in this case)
}

Now that we know how it is used, we're mainly interested in the implementation. The following code is from the .NET Framework. It uses the interface IExceptionFilterinternally as a contract:

现在我们知道它是如何使用的,我们主要对实现感兴趣。以下代码来自 .NET Framework。它在IExceptionFilter内部使用接口作为契约:

namespace System.Web.Http.Filters
{
    public interface IExceptionFilter : IFilter
    {
        // Executes an asynchronous exception filter.
        // Returns: An asynchronous exception filter.
        Task ExecuteExceptionFilterAsync(
                    HttpActionExecutedContext actionExecutedContext, 
                    CancellationToken cancellationToken);
    }
}

The ExceptionFilterAttributeitself is defined as follows:

ExceptionFilterAttribute本身被定义为如下:

namespace System.Web.Http.Filters
{
    // Represents the attributes for the exception filter.
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, 
            Inherited = true, AllowMultiple = true)]
    public abstract class ExceptionFilterAttribute : FilterAttribute, 
            IExceptionFilter, IFilter
    {
        // Raises the exception event.
        // actionExecutedContext: The context for the action.
        public virtual void OnException(
            HttpActionExecutedContext actionExecutedContext)
        {
        }
        // Asynchronously executes the exception filter.
        // Returns: The result of the execution.
        Task IExceptionFilter.ExecuteExceptionFilterAsync(
            HttpActionExecutedContext actionExecutedContext, 
            CancellationToken cancellationToken)
        {
            if (actionExecutedContext == null)
            {
                throw Error.ArgumentNull("actionExecutedContext");
            }
            this.OnException(actionExecutedContext);
            return TaskHelpers.Completed();
        }
    }
}

Inside ExecuteExceptionFilterAsync, the method OnExceptionis called. Because you have overridden it as shown earlier, the error can now be handled by your own code.

在里面ExecuteExceptionFilterAsync,方法OnException被调用。因为您已经如前面所示覆盖了它,所以现在可以通过您自己的代码处理该错误。



There is also a commercial product available as mentioned in OwenP's answer, PostSharp, which allows you to do that easily. Hereis an example how you can do that with PostSharp. Note that there is an Express edition available which you can use for free even for commercial projects.

OwenP 的回答PostSharp 中也提到了一个商业产品,它可以让您轻松地做到这一点。是一个如何使用 PostSharp 执行操作的示例。请注意,有可用的 Express 版本,即使用于商业项目,您也可以免费使用。

PostSharp Example(see the link above for full description):

PostSharp 示例(有关完整说明,请参阅上面的链接):

public class CustomerService
{
    [RetryOnException(MaxRetries = 5)]
    public void Save(Customer customer)
    {
        // Database or web-service call.
    }
}

Here the attribute specifies that the Savemethod is called up to 5 times if an exception occurs. The following code defines this custom attribute:

这里的属性指定Save如果发生异常,该方法最多被调用 5 次。以下代码定义了此自定义属性:

[PSerializable]
public class RetryOnExceptionAttribute : MethodInterceptionAspect
{
    public RetryOnExceptionAttribute()
    {
        this.MaxRetries = 3;
    }

    public int MaxRetries { get; set; }

    public override void OnInvoke(MethodInterceptionArgs args)
    {
        int retriesCounter = 0;

        while (true)
        {
            try
            {
                args.Proceed();
                return;
            }
            catch (Exception e)
            {
                retriesCounter++;
                if (retriesCounter > this.MaxRetries) throw;

                Console.WriteLine(
                  "Exception during attempt {0} of calling method {1}.{2}: {3}",
                  retriesCounter, args.Method.DeclaringType, args.Method.Name, e.Message);
            }
        }
    }
}

回答by balintn

You might take a look at the poor man's solution: see the decorator pattern.

您可能会看一下穷人的解决方案:查看装饰者模式。