【问题标题】:C#: How to create an attribute on a method triggering an event when it is invoked?C#:如何在调用时触发事件的方法上创建属性?
【发布时间】:2010-09-18 14:12:30
【问题描述】:

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

我的意思是这样的:

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

我完全不知道该怎么做,或者是否可能,但System.Diagnostic.ConditionalAttribute 可能会在后台做类似的事情。不过我不确定。

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

【问题讨论】:

  • 简短的回答是:是的,伴随着:我不知道怎么做。您要做的是在编译时进行 IL 操作,以便在方法的第一行和最后一行注入回调。 MS.VB 程序集有一个执行某些 IL 操作的属性(使类成为非静态单例)。
  • @Tamas:我知道你的问题很久以前就被问过了,但我想出了一个答案。希望它可以帮助你。 :-)

标签: c# .net events methods attributes


【解决方案1】:

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

【讨论】:

  • 我对这个答案投了赞成票,因为这也是我的想法:您需要在构建后运行一些工具来分析您的 IL,查找具有您的属性的方法,并注入一些事件逻辑。
  • 我刚刚发现了 PostSharp,并开始寻找这个问题并将其发布为答案。
【解决方案2】:

我认为没有办法只使用属性,但是使用proxy classes 和反射,您可以拥有一个知道拦截您拥有属性方法的类的实例化的类。

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

【讨论】:

    【解决方案3】:

    属性提供信息,它们是元数据。我不知道有什么方法可以临时做这件事,有人可能会。

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

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

    【讨论】:

      【解决方案4】:

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

      基本上,它们是您的对象的子类并覆盖此方法...

      然后变成:

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

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

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

        • 链接好像坏了。它指的是:MSDN Magazine Issues and Downloads,列出了该杂志的所有问题。因为答案没有提供任何进一步的信息,所以如果没有有效的链接,它就无济于事。
        • 如果有人正在寻找同样的例子,可以检查 git github.com/stdeepak22/CSharp_Method_Interceptor
        【解决方案6】:

        此概念用于 MVC 网络应用程序。

        .NET Framework 4.x 提供了多个触发动作的属性,例如:ExceptionFilterAttribute(处理异常)、AuthorizeAttribute(处理授权)。两者都在System.Web.Http.Filters 中定义。

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

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

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

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

        每当Post方法被调用时,它都会在myAuthorization属性内调用IsAuthorized方法Post方法内的代码被执行之前。

        如果您在IsAuthorized 方法中返回false,则表示未授予授权并且方法Post 的执行将中止。


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

        要使用它,从ExceptionFilterAttribute 派生DivideByZeroExceptionFilter 类,如here 所示,并覆盖方法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
                        };
                }
            }
        }
        

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

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

        现在我们知道它是如何使用的,我们主要对实现感兴趣。以下代码来自 .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);
            }
        }
        

        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();
                }
            }
        }
        

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


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

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

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

        这里属性指定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);
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案7】:

          你可以看看穷人的解决方案:看看装饰器模式。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-03-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-21
            • 2020-10-02
            • 1970-01-01
            相关资源
            最近更新 更多