3

ご覧いただきありがとうございます。

バックグラウンド

特定のメソッドをラップするために使用される拡張メソッドがあり、try/catchキャッチされた例外をログに記録するためのコードを追加しています。

 public static T HandleServerError<T>(this Func<T> func)
        {
            T result = default(T);
            try
            {
                result = func();
            }
            catch (Exception ex)
            {
                //******************************
                //Code for logging will go here.
                //******************************

                ErrorHandlers.ThrowServerErrorException(ex);
            }

            return result;
        }

メソッドの呼び出し方法は次のとおりです。

var result = new Func<SomeClass.SomeType>(() => SomeClass.SomeMethod(id, name, color, quantity)).HandleServerError();
return result;

ご覧のとおり、呼び出しているメソッドはすべて拡張メソッドに挿入され、try/catch 内で実行されます。

ロギングには NLog または ELMAH を使用しますが、それはこの質問にはほとんど関係ありません。

問題

何か問題が発生した場合は、「オブジェクト参照がオブジェクトのインスタンスに設定されていません」などの情報自体は役に立たないため、委任されたメソッドに関する情報をできるだけ多くログに記録する必要があります。

呼び出されるメソッドのクラスと名前、およびメソッド シグネチャのパラメータとその値をログに記録したいと思います。可能であれば、失敗した行をログに記録し、最後に実際のスタック トレースを記録したいと思います。

これにはリフレクションを使用する必要があり、注入されたメソッドの実行時に何らかの形でバインディング フラグをキャッチする必要があると推測していますが、それが最善のアプローチであるか、それが実現可能であるかは完全にはわかりません。

質問

C# を使用して、注入/委任されたメソッドに関するメタ情報 (つまり、メソッド名、元のクラス、パラメーター、パラメーター値) を取得するにはどうすればよいですか?

前もって感謝します。

4

2 に答える 2

4

このロギング分野横断的な問題をアプリケーションに追加する方法を改善できる可能性があるように私には思えます。

ここでの主な問題は、ソリューションによってSomeClass.SomeMethod(または呼び出されたメソッド) に変更を加えることはできませんが、使用するコードに変更を加える必要があることです。つまり、既存のコードを変更せずにこの種の変更を行うことが可能でなければならないというオープン/クローズの原則を破っています。

大げさだと思うかもしれませんがHandleServerError、アプリケーションにはすでに 100 を超える呼び出しがあり、呼び出しの数は増える一方です。そして、これらの「関数型デコレーター」をさらにシステムに追加する予定です。承認チェック、メソッド引数の検証、インストルメンテーション、または監査追跡を行うことについて考えたことはありますか? そして、あなたはそれをするnew Func<T>(() => someCall).HandleServerError()のが面倒だと認めなければなりませんね。

システムに適切な抽象化を導入することで、実際の質問の問題を含め、これらすべての問題を解決できます。

最初のステップは、指定されたメソッド引数をParameter Objectに昇格させることです:

public SomeMethodParameters
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Color Color { get; set; }
    public decimal Quantity { get; set; }

    public decimal Result { get; set; }
}

個々の引数をすべてメソッドに渡す代わりに、それらをすべて 1 つのオブジェクトとしてまとめて渡すことができます。それは何の役に立ちますか?読む。

SomeClass.SomeMethod 2 番目のステップは、背後にある (または実際には任意のメソッド)の実際のロジックを非表示にする汎用インターフェイスを導入することです。

public interface IMethodHandler<TParameter>
{
    void Handle(TParameter parameter);
}

IMethodHandler<TParameter>システム内の (ビジネス) 操作ごとに、実装を作成できます。あなたの場合、次のSomeClass.SomeMethodように、への呼び出しをラップする実装を簡単に作成できます。

public class SomeMethodHandler 
    : IMethodHandler<SomeMethodParameters>
{
    void Handle(SomeMethodParameters parameter)
    {
        parameter.Result = SomeClass.SomeMethod(
            parameter.id,
            parameter.Name, 
            parameter.Color, 
            parameter.Quantity);
    }
}

このようなことを行うのは少しばかげているように見えるかもしれませんが、この設計を迅速に実装し、 static のロジックをSomeClass.SomeMethod内に移動することができますSomeMethodHandler

3番目のステップは、消費者IMethodHandler<SomeMethodParameters>がシステム内の静的メソッドに依存するのではなく、インターフェースに依存できるようにすることです(あなたの場合は再びSomeClass.SomeMethod.)。このような抽象化に依存することの利点を少し考えてみてください。

これによる興味深い結果の 1 つは、コンシューマーの単体テストがはるかに簡単になることです。しかし、単体テストには興味がないかもしれません。しかし、あなたは疎結合に興味があります。消費者が実際の実装 (特に静的メソッド) ではなく、そのような抽象化に依存している場合、ロギングなどの分野横断的な懸念事項を追加するなど、あらゆる種類のクレイジーなことを行うことができます。IMethodHandler<T>これを行う良い方法は、実装をデコレータでラップすることです。ユースケースのデコレータは次のとおりです。

public class LoggingMethodHandlerDecorator<T> 
    : IMethodHandler<T>
{
    private readonly IMethodHandler<T> handler;

    public LoggingMethodHandlerDecorator(
        IMethodHandler<T> handler)
    {
        this.handler = handler;
    }

    public void Handle(T parameters)
    {
        try
        {
            this.handler.Handle(parameters);
        }
        catch (Exception ex)
        {
            //******************************
            //Code for logging will go here.
            //******************************

            ErrorHandlers.ThrowServerErrorException(ex);

            throw;
        }
    }
}

Handleこのデコレーターのメソッドに、元のメソッドのコードがどのように含まれているかがわかりますHandleServerError<T>か? HandleServerError実際には、元のメソッドの動作を新しい動作で「装飾」(または「拡張」) したため、既に行っていたこととそれほど違いはありません。しかし、現在メソッド呼び出しを使用する代わりに、オブジェクトを使用しています。

このすべての良い点は、この単一のジェネリックをすべての単一の実装LoggingMethodHandlerDecorator<T>にラップでき、すべてのコンシューマーが使用できることです。IMethodHandler<T>このようにして、消費者とメソッドの両方がこれを知ることなく、ロギングなどの横断的な懸念を追加できます。これがオープン/クローズの原則です。

しかし、これには他にも本当に素晴らしいことがあります。最初の質問は、メソッド名とパラメーターに関する情報を取得する方法に関するものでした。Funcデリゲート内にラップされたカスタム メソッドを呼び出す代わりに、すべての引数をオブジェクト内にラップしたため、このすべての情報を簡単に利用できるようになりました。catch次のように句を実装できます。

string messageInfo = string.Format("<{0}>{1}</{0}>",
    parameters.GetType().Name, string.Join("",
        from property in parameters.GetType().GetProperties()
        where property.CanRead
        select string.Format("<{0}>{1}</{0}>",
            property.Name, property.GetValue(parameters, null)));

これにより、TParameter オブジェクトの名前とその値が XML 形式にシリアル化されます。もちろん、.NET を使用XmlSerializerしてオブジェクトを XML にシリアル化するか、必要なその他のシリアル化を使用することもできます。メタデータで利用可能な場合はすべての情報。これは非常に優れています。パラメータ オブジェクトに適切で一意の名前を付けると、ログ ファイルですぐに識別することができます。そして、実際のパラメーターとおそらくいくつかのコンテキスト情報 (日時、現在のユーザーなど) とともに、バグを修正するために必要なすべての情報が得られます。

LoggingMethodHandlerDecorator<T>これと元の の間に 1 つの違いがありHandleServerError<T>、それが最後のthrowステートメントです。あなたの実装ON ERROR RESUME NEXTは、最善の方法ではないかもしれない何らかの種類を実装しています。メソッドが失敗したときに続行する (そしてデフォルト値を返す) ことは実際に安全ですか? 私の経験では、通常はそうではなく、この時点で続行すると、消費するクラスを作成する開発者は、すべてが期待どおりに機能すると考えたり、アプリケーションのユーザーに、すべてが期待どおりに機能したと考えさせたりする可能性があります (彼の変更たとえば、保存されていましたが、実際には保存されていませんでした)。通常、これについてできることはあまりなく、すべてをcatchこの情報をログに記録したいことは想像できますが、ステートメントは悪化するだけです。「アプリケーションは常に動作する必要がある」または「エラー ページを表示したくない」などのユーザー要件に惑わされないでください。すべてのエラーを抑制してこれらの要件を実装しても、根本的な原因を解決することはできません。それでも、本当にキャッチ アンド コンティニュが必要な場合は、`throwステートメントを削除するだけで、元の動作に戻ります。

システムを設計するこの方法について詳しく知りたい場合は、ここから始めてください

于 2012-11-10T15:27:35.390 に答える
2

基本的に他のデリゲートであるため、そのプロパティMethodとプロパティに簡単にアクセスできます。Target

func.Methodとを使用するだけfunc.Targetです。

于 2012-11-09T15:21:12.230 に答える