156

特定のクラスについて、トレース機能が必要です。つまり、すべてのメソッド呼び出し (メソッド シグネチャと実際のパラメーター値) とすべてのメソッド終了 (メソッド シグネチャのみ) をログに記録したいと考えています。

次のことを前提として、これを達成するにはどうすればよいですか。

  • C# 用にサードパーティの AOP ライブラリを使用したくありません。
  • トレースしたいすべてのメソッドに重複したコードを追加したくありません。
  • クラスのパブリック API を変更したくありません。クラスのユーザーは、まったく同じ方法ですべてのメソッドを呼び出せるはずです。

質問をより具体的にするために、3 つのクラスがあると仮定します。

 public class Caller 
 {
     public static void Call() 
     {
         Traced traced = new Traced();
         traced.Method1();
         traced.Method2(); 
     }
 }

 public class Traced 
 {
     public void Method1(String name, Int32 value) { }

     public void Method2(Object object) { }
 }

 public class Logger
 {
     public static void LogStart(MethodInfo method, Object[] parameterValues);

     public static void LogEnd(MethodInfo method);
 }

Caller.Callメソッドを変更せずに、およびTraced.Method1およびTraced.Method2への呼び出しを明示的に追加せずに、 Method1およびMethod2への呼び出しごとにLogger.LogStartおよびLogger.LogEndを呼び出すにはどうすればよいですか?

編集: Call メソッドを少し変更することが許可されている場合、どのような解決策がありますか?

4

16 に答える 16

72

C# は AOP 指向の言語ではありません。いくつかの AOP 機能があり、他のいくつかをエミュレートできますが、C# で AOP を作成するのは面倒です。

あなたがやりたいことを正確に行う方法を探しましたが、それを行う簡単な方法は見つかりませんでした.

私が理解しているように、これはあなたがやりたいことです:

[Log()]
public void Method1(String name, Int32 value);

そのためには 2 つの主なオプションがあります

  1. クラスを MarshalByRefObject または ContextBoundObject から継承し、IMessageSink から継承する属性を定義します。この記事に良い例があります。ただし、MarshalByRefObject を使用するとパフォーマンスが大幅に低下することを考慮する必要があります。つまり、10 倍のパフォーマンスが失われることについて話しているので、試す前に慎重に検討してください。

  2. もう 1 つのオプションは、コードを直接挿入することです。つまり、リフレクションを使用してすべてのクラスを「読み取り」、その属性を取得し、適切な呼び出しを挿入する必要があります (さらに言えば、Reflection.Emit が使用すると思うように、Reflection.Emit メソッドを使用できなかったと思います)。既存のメソッド内に新しいコードを挿入することはできません)。設計時に、これは CLR コンパイラの拡張機能を作成することを意味しますが、それがどのように行われるかは正直わかりません。

最後のオプションは、IoC フレームワークを使用することです。ほとんどの IoC フレームワークは、メソッドをフックできるようにするエントリ ポイントを定義することによって機能するため、これは完璧なソリューションではないかもしれませんが、達成したいことによっては、それがかなりの近似になる可能性があります。

于 2008-08-25T09:31:40.953 に答える
49

これを実現する最も簡単な方法は、おそらくPostSharpを使用することです。適用する属性に基づいて、メソッド内にコードを挿入します。それはあなたが望むことを正確に行うことを可能にします。

もう 1 つのオプションは、プロファイリング APIを使用してメソッド内にコードを挿入することですが、これは非常に困難です。

于 2008-08-25T09:41:12.740 に答える
7

IDisposable インターフェイスを実装するクラス (Tracing と呼びます) を作成すると、すべてのメソッド本体を

Using( Tracing tracing = new Tracing() ){ ... method body ...}

Tracing クラスでは、コンストラクター/Dispose メソッドでそれぞれトレースのロジックを処理し、Tracing クラスでメソッドの開始と終了を追跡できます。そのような:

    public class Traced 
    {
        public void Method1(String name, Int32 value) {
            using(Tracing tracer = new Tracing()) 
            {
                [... method body ...]
            }
        }

        public void Method2(Object object) { 
            using(Tracing tracer = new Tracing())
            {
                [... method body ...]
            }
        }
    }
于 2008-08-25T09:12:02.920 に答える
5

私はもっ​​と簡単かもしれない別の方法を見つけました...

メソッドの宣言 InvokeMethod

[WebMethod]
    public object InvokeMethod(string methodName, Dictionary<string, object> methodArguments)
    {
        try
        {
            string lowerMethodName = '_' + methodName.ToLowerInvariant();
            List<object> tempParams = new List<object>();
            foreach (MethodInfo methodInfo in serviceMethods.Where(methodInfo => methodInfo.Name.ToLowerInvariant() == lowerMethodName))
            {
                ParameterInfo[] parameters = methodInfo.GetParameters();
                if (parameters.Length != methodArguments.Count()) continue;
                else foreach (ParameterInfo parameter in parameters)
                    {
                        object argument = null;
                        if (methodArguments.TryGetValue(parameter.Name, out argument))
                        {
                            if (parameter.ParameterType.IsValueType)
                            {
                                System.ComponentModel.TypeConverter tc = System.ComponentModel.TypeDescriptor.GetConverter(parameter.ParameterType);
                                argument = tc.ConvertFrom(argument);

                            }
                            tempParams.Insert(parameter.Position, argument);

                        }
                        else goto ContinueLoop;
                    }

                foreach (object attribute in methodInfo.GetCustomAttributes(true))
                {
                    if (attribute is YourAttributeClass)
                    {
                        RequiresPermissionAttribute attrib = attribute as YourAttributeClass;
                        YourAttributeClass.YourMethod();//Mine throws an ex
                    }
                }

                return methodInfo.Invoke(this, tempParams.ToArray());
            ContinueLoop:
                continue;
            }
            return null;
        }
        catch
        {
            throw;
        }
    }

次に、メソッドを次のように定義します

[WebMethod]
    public void BroadcastMessage(string Message)
    {
        //MessageBus.GetInstance().SendAll("<span class='system'>Web Service Broadcast: <b>" + Message + "</b></span>");
        //return;
        InvokeMethod("BroadcastMessage", new Dictionary<string, object>() { {"Message", Message} });
    }

    [RequiresPermission("editUser")]
    void _BroadcastMessage(string Message)
    {
        MessageBus.GetInstance().SendAll("<span class='system'>Web Service Broadcast: <b>" + Message + "</b></span>");
        return;
    }

これで、依存性注入なしで実行時にチェックを行うことができます...

サイトに落とし穴はありません:)

これが AOP フレームワークや MarshalByRefObject からの派生、またはリモート処理やプロキシ クラスの使用よりも軽量であることに同意していただければ幸いです。

于 2011-03-17T20:45:17.633 に答える
5

これを見てください - かなり重いもの.. http://msdn.microsoft.com/en-us/magazine/cc164165.aspx

Essential .net - ドン ボックスには、傍受と呼ばれる必要なものに関する章がありました。私はそれのいくつかをここにこすり落としました(フォントの色について申し訳ありません-当時は暗いテーマを持っていました...) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html

于 2008-08-25T09:12:17.133 に答える
4

まず、(MarshalByRefObject を実装するのではなく) インターフェイスを実装するようにクラスを変更する必要があります。

interface ITraced {
    void Method1();
    void Method2()
}
class Traced: ITraced { .... }

次に、装飾されたオブジェクトへの呼び出しをインターセプトできるようにインターフェイスを装飾するために、RealProxy に基づく汎用ラッパー オブジェクトが必要です。

class MethodLogInterceptor: RealProxy
{
     public MethodLogInterceptor(Type interfaceType, object decorated) 
         : base(interfaceType)
     {
          _decorated = decorated;
     }

    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = msg as IMethodCallMessage;
        var methodInfo = methodCall.MethodBase;
        Console.WriteLine("Precall " + methodInfo.Name);
        var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
        Console.WriteLine("Postcall " + methodInfo.Name);

        return new ReturnMessage(result, null, 0,
            methodCall.LogicalCallContext, methodCall);
    }
}

これで、ITraced の Method1 と Method2 への呼び出しをインターセプトする準備が整いました。

 public class Caller 
 {
     public static void Call() 
     {
         ITraced traced = (ITraced)new MethodLogInterceptor(typeof(ITraced), new Traced()).GetTransparentProxy();
         traced.Method1();
         traced.Method2(); 
     }
 }
于 2015-08-26T23:40:45.563 に答える
2

CodePlex でオープン ソース フレームワークCInjectを使用できます。Injector を作成するための最小限のコードを記述し、CInject を使用して任意のコードをすばやくインターセプトすることができます。さらに、これはオープン ソースであるため、これを拡張することもできます。

または、 IL を使用したメソッド呼び出しのインターセプトに関するこの記事に記載されている手順に従い、C# で Reflection.Emit クラスを使用して独自のインターセプターを作成することもできます。

于 2012-12-11T02:34:04.953 に答える
1

GOFデコレータパターンを使用して、トレースが必要なすべてのクラスを「装飾」することができます。

これはおそらくIOCコンテナでのみ実際に実用的です(ただし、先に示したように、IOCパスをたどる場合は、メソッドのインターセプトを検討することをお勧めします)。

于 2008-09-18T16:31:19.263 に答える
1

彼がどのようにそれをしたかについての答えを得るには、 Ayendeにバグを報告する必要があります。

于 2009-11-21T06:34:06.630 に答える
1

解決策はわかりませんが、私のアプローチは次のとおりです。

クラス (またはそのメソッド) をカスタム属性で装飾します。プログラムの別の場所で、初期化関数にすべての型を反映させ、属性で修飾されたメソッドを読み取り、IL コードをメソッドに挿入します。メソッドを、実際のメソッドを呼び出しから. さらに、リフレクションを使用してメソッドを変更できるかどうかわからないため、型全体を置き換える方が実用的かもしれません。LogStartLogEnd

于 2008-08-25T09:07:28.220 に答える
1

AOP はコードをきれいに実装するために必須ですが、C# でブロックを囲みたい場合は、ジェネリック メソッドの方が比較的簡単に使用できます。(インテリ センスと厳密に型指定されたコードを使用) 確かに、AOP の代わりにはなりません。

PostSHarpにはバグの問題はほとんどありませんが(本番環境で使用する自信がありません)、良い機能です。

汎用ラッパー クラス、

public class Wrapper
{
    public static Exception TryCatch(Action actionToWrap, Action<Exception> exceptionHandler = null)
    {
        Exception retval = null;
        try
        {
            actionToWrap();
        }
        catch (Exception exception)
        {
            retval = exception;
            if (exceptionHandler != null)
            {
                exceptionHandler(retval);
            }
        }
        return retval;
    }

    public static Exception LogOnError(Action actionToWrap, string errorMessage = "", Action<Exception> afterExceptionHandled = null)
    {
        return Wrapper.TryCatch(actionToWrap, (e) =>
        {
            if (afterExceptionHandled != null)
            {
                afterExceptionHandled(e);
            }
        });
    }
}

使用法は次のようになります(もちろんインテリ感覚で)

var exception = Wrapper.LogOnError(() =>
{
  MessageBox.Show("test");
  throw new Exception("test");
}, "Hata");
于 2011-05-12T22:45:09.503 に答える
-1
  1. 独自の AOP ライブラリを作成します。
  2. リフレクションを使用して、インスタンスに対してログ プロキシを生成します (既存のコードの一部を変更せずに実行できるかどうかはわかりません)。
  3. アセンブリを書き直して、ロギング コードを挿入します (基本的には 1 と同じ)。
  4. CLR をホストし、このレベルでログを追加します (これは実装が最も難しいソリューションだと思いますが、CLR に必要なフックがあるかどうかはわかりません)。
于 2008-08-25T09:06:09.487 に答える