web-dev-qa-db-ja.com

C#:呼び出されたときにイベントをトリガーするメソッドに属性を作成する方法は?

C#または.NETで、メソッドが呼び出されたときにイベントをトリガーするメソッドに属性を作成する方法はありますか?理想的には、メソッドの呼び出しの前後にカスタムアクションを実行できます。

私はこのようなものを意味します:

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

私はそれをどのように行うか、または可能であれば完全に無知ですが、 System.Diagnostic.ConditionalAttribute は、バックグラウンドで同様のことを行う可能性があります。よくわかりません。

[〜#〜]編集[〜#〜]:私は特定のケースの状況により、パフォーマンスは実際には問題ではないことを言及し忘れました。

40
Tamas Czinege

これを行う方法を知っている唯一の方法は PostSharp を使用することです。それはあなたのILを後処理し、あなたが要求したもののようなことをすることができます。

17
OwenP

この概念は、[〜#〜] mvc [〜#〜] Webアプリケーションで使用されます。

。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メソッドが呼び出されますbeforePostメソッド内のコードが実行されます。

falseメソッドでIsAuthorizedを返すと、認証が付与されず、メソッドPostの実行が中止されます。


これがどのように機能するかを理解するために、別の例を見てみましょう:ExceptionFilterは、属性を使用して例外をフィルタリングできるため、使用方法は上記のようになります。 AuthorizeAttribute(使用方法についての詳細な説明 here があります)。

これを使用するには、次に示すように、DivideByZeroExceptionFilterからExceptionFilterAttributeクラスを派生させ、 here メソッドをオーバーライドして、メソッド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)
{
    // 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.</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でこれを行う方法の例です。商用プロジェクトでも無料で使用できる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);
            }
        }
    }
}
24
Matt

アスペクト指向のフレームワークが必要です。 PostSharpは Windsor のようにそれを行います。

基本的に、それらはオブジェクトをサブクラス化し、このメソッドをオーバーライドします...

その後、次のようになります。

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

     _innerClass.DoSomeStuff();
}

もちろん、これらすべてはあなたには隠されています。あなたがしなければならないのは、ウィンザーにタイプを尋ねるだけであり、それはあなたのためにプロキシを行います。この属性は、ウィンザーで考える(カスタム)施設になります。

11
Ben Scheirman

ContextBoundObjectとIMessageSinkを使用できます。 http://msdn.Microsoft.com/nb-no/magazine/cc301356(en-us).aspx を参照してください

このアプローチは、直接メソッド呼び出しと比較してパフォーマンスに重大な影響を与えることに注意してください。

3
Hallgrim

私は属性だけでそれを行う方法はないと思いますが、 proxy classes とリフレクションを使用すると、属性付きのメソッドがあるクラスのインスタンス化をインターセプトすることを知っているクラスができます。

その後、プロキシクラスは、属性付きメソッドが呼び出されるたびにイベントをトリガーできます。

0
wprl

属性は情報を提供し、それらはメタデータです。誰かがこれを手際よく行う方法を知りません。

いくつかの軽量なイベント処理を可能にする.NETの部分メソッドを見ることができます。フックを提供し、他の誰かに実装を処理させます。メソッドが実装されていない場合、コンパイラはそれを無視します。

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

0
Nick

あなたは貧乏人の解決策を見てみるかもしれません:デコレータパターンを見てください。

0
balintn