C #: Как создать атрибут метода, инициирующего событие при его вызове? - PullRequest
39 голосов
/ 22 октября 2008

Есть ли способ в C # или .NET вообще создать атрибут метода, который вызывает событие при вызове метода? В идеале я мог бы запускать пользовательские действия до и после вызова метода.

Я имею в виду что-то вроде этого:

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

Я совершенно не знаю, как это сделать или вообще возможно, но System.Diagnostic.ConditionalAttribute может сделать аналогичную вещь в фоновом режиме. Хотя я не уверен.

РЕДАКТИРОВАТЬ : Я забыл упомянуть, что из-за обстоятельств моего конкретного случая, производительность на самом деле не проблема.

Ответы [ 7 ]

21 голосов
/ 30 января 2014

Эта концепция используется в веб-приложениях 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, он вызывает метод IsAuthorized внутри атрибута myAuthorization до кода внутри метода Post.

Если вы возвращаете false в методе IsAuthorized, вы указываете, что авторизация не предоставлена ​​и выполнение метода Post прекращается.


Чтобы понять, как это работает, давайте рассмотрим другой пример: ExceptionFilter, который позволяет фильтровать исключения с помощью атрибутов, использование аналогично тому, как показано выше для AuthorizeAttribute (вы можете более подробное описание его использования здесь ).

Чтобы его использовать, выведите класс 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("An error occured within the application.",
                                System.Text.Encoding.UTF8, "text/plain"), 
                StatusCode = System.Net.HttpStatusCode.InternalServerError
                };
        }
    }
}

Затем используйте следующий демонстрационный код для его запуска:

[DivideByZeroExceptionFilter]
public void Delete(int id)
{
    // causes the DivideByZeroExceptionFilter attribute to be triggered:
    throw new DivideByZeroException(); 
}

Теперь, когда мы знаем, как он используется, мы в основном заинтересованы в его реализации. Следующий код взят из .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.</param>
        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 , который позволяет вам легко это делать. Здесь - пример того, как вы можете сделать это с PostSharp. Обратите внимание, что доступна экспресс-версия, которую вы можете использовать бесплатно даже для коммерческих проектов.

Пример 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);
            }
        }
    }
}
17 голосов
/ 22 октября 2008

Единственный способ, которым я знаю, как это сделать, это PostSharp . Он обрабатывает ваш IL и может делать то, что вы просили.

10 голосов
/ 22 октября 2008

Вам нужен какой-то аспектно-ориентированный фреймворк. PostSharp сделает это, как и Windsor .

По сути, они наследуют ваш объект и переопределяют этот метод ...

тогда становится:

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

     _innerClass.DoSomeStuff();
}

Конечно, все это скрыто от вас. Все, что вам нужно сделать, это спросить Виндзор о типе, и он сделает прокси для вас. Атрибут становится (пользовательским) средством, которое я думаю в Виндзоре.

3 голосов
/ 22 октября 2008

Вы можете использовать ContextBoundObject и IMessageSink. Смотри http://msdn.microsoft.com/nb-no/magazine/cc301356(en-us).aspx

Имейте в виду, что этот подход сильно влияет на производительность по сравнению с прямым вызовом метода.

0 голосов
/ 07 мая 2018

Вы можете взглянуть на решение бедняка: см. Образец декоратора.

0 голосов
/ 22 октября 2008

Атрибут дает информацию, это метаданные. Я не знаю, как это сделать, кто-то может.

Вы можете посмотреть на частичные методы в .NET, которые позволяют вам выполнять некоторую облегченную обработку событий. Вы предоставляете хуки и позволяете кому-то другому заниматься реализацией. Если метод не реализован, компилятор просто игнорирует его.

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

0 голосов
/ 22 октября 2008

Я не думаю, что есть способ сделать это только с помощью атрибута, но используя прокси-классы и отражение, у вас может быть класс, который знает, что он перехватывает экземпляры классов, к которым вы приписали методы.

Тогда прокси-класс может инициировать событие при каждом вызове атрибутивных методов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...