كيف يمكنني اعتراض استدعاء الأسلوب في C# ؟

StackOverflow https://stackoverflow.com/questions/25803

  •  09-06-2019
  •  | 
  •  

سؤال

لفئة معينة أود أن تتبع أي وظيفةأود أن سجل كل استدعاء الأسلوب (أسلوب التوقيع الفعلي قيم المعلمة) و كل طريقة خروج (فقط طريقة التوقيع).

كيف يمكنني تحقيق ذلك على افتراض أن:

  • أنا لا ترغب في استخدام أي 3rd الطرف اوب مكتبات C# ،
  • أنا لا ترغب في إضافة التعليمات البرمجية مكررة لجميع الطرق التي كنت تريد أن تتبع ،
  • أنا لا أريد أن تغيير 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);
 }

كيف يمكنني الاحتجاج المسجل.LogStart و المسجل.LogEnd كل دعوة إلى Method1 و Method2 دون تعديل المتصل.الاتصال طريقة دون إضافة يدعو صراحة إلى تتبع.Method1 و تتبع.Method2?

تحرير:ماذا يكون الحل إذا كان مسموح لي تغيير طفيف في استدعاء الأسلوب ؟

هل كانت مفيدة؟

المحلول

C# لا اوب اللغة الموجهة.لديه بعض اوب الميزات يمكنك محاكاة بعض الآخرين ولكن جعل اوب مع C# مؤلمة.

بحثت عن سبل تفعل بالضبط ما تريد القيام به و لم أجد أي طريقة سهلة للقيام بذلك.

كما أفهم أن هذا هو ما تريد القيام به:

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

و من أجل القيام بذلك لديك خيارين رئيسيين

  1. ترث الفئة من MarshalByRefObject أو ContextBoundObject و تحديد السمة التي يرث من IMessageSink. هذه المادة وقد مثال جيد.يجب عليك أن تنظر في تخصصها أن استخدام MarshalByRefObject الأداء سوف تذهب إلى أسفل مثل الجحيم ، أعني ، أنا أتحدث عن 10x الأداء فقدت لذلك فكر جيدا قبل أن تحاول ذلك.

  2. الخيار الآخر هو أن حقن التعليمات البرمجية مباشرة.في وقت التشغيل ، وهذا يعني سيكون لديك لاستخدام التفكير إلى "قراءة" كل فئة على خصائصه وحقن appropiate دعوة (وفي هذا الشأن أعتقد أنك لا يمكن استخدام التفكير.تنبعث منها الأسلوب كما أعتقد التفكير.تنبعث منها لن تسمح لك بإدراج رمز جديد داخل القائمة بالفعل طريقة).في وقت التصميم هذا يعني خلق امتدادا إلى CLR المترجم الذي لدي بصراحة فكرة عن كيف يتم ذلك.

الخيار النهائي هو استخدام IoC إطار.ربما انها ليست الحل الأمثل حيث أن معظم الأولمبية الدولية أطر يعمل من خلال تحديد نقاط الدخول التي تسمح أساليب يكون مدمن مخدرات ولكن اعتمادا على ما كنت تريد أن يحققوه ، التي قد تكون عادلة aproximation.

نصائح أخرى

أبسط طريقة لتحقيق ذلك هو على الأرجح إلى استخدام PostSharp.تحقن كود داخل وسائل الخاص بك على أساس السمات التي تنطبق عليه.فإنه يسمح لك أن تفعل بالضبط ما تريد.

وثمة خيار آخر هو استخدام التنميط API لحقن كود داخل الأسلوب ولكن هذا هو حقا المتشددين.

إذا كنت أكتب فئة نسميها تتبع - أن تنفذ واجهة IDisposable, يمكنك لف كل طريقة من الهيئات

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

في تتبع فئة يمكن التعامل مع منطق آثار في منشئ/أسلوب التخلص ، على التوالي ، في تتبع فئة لتتبع الدخول والخروج من الأساليب.مثل:

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

هل يمكن تحقيق ذلك مع اعتراض الميزة دي حاوية مثل قلعة وندسور.والواقع أنه من الممكن تكوين حاوية في مثل هذه الطريقة أن كل الفئات التي لديها طريقة زينت بها سمة معينة سيتم اعتراضها.

بخصوص النقطة رقم 3, OP سئل عن حل دون اوب الإطار.افترضت في الجواب التالي أن ما ينبغي تجنبه كان الجانب ، JointPoint, PointCut ، إلخ.وفقا اعتراض على الوثائق CastleWindsor, لا شيء من تلك المطلوبة لإنجاز ما يطلب.

تكوين عام تسجيل اعتراضية على أساس وجود سمة:

public class RequireInterception : IContributeComponentModelConstruction
{
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        if (HasAMethodDecoratedByLoggingAttribute(model.Implementation))
        {
            model.Interceptors.Add(new InterceptorReference(typeof(ConsoleLoggingInterceptor)));
            model.Interceptors.Add(new InterceptorReference(typeof(NLogInterceptor)));
        }
    }

    private bool HasAMethodDecoratedByLoggingAttribute(Type implementation)
    {
        foreach (var memberInfo in implementation.GetMembers())
        {
            var attribute = memberInfo.GetCustomAttributes(typeof(LogAttribute)).FirstOrDefault() as LogAttribute;
            if (attribute != null)
            {
                return true;
            }
        }

        return false;
    }
}

إضافة خلق IContributeComponentModelConstruction الحاوية ،

container.Kernel.ComponentModelBuilder.AddContributor(new RequireInterception());

ويمكنك أن تفعل ما تريد في اعتراضية نفسها

public class ConsoleLoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.Writeline("Log before executing");
        invocation.Proceed();
        Console.Writeline("Log after executing");
    }
}

إضافة خاصية تسجيل الخاص بك طريقة تسجيل الدخول

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

     [Log]
     public void Method2(Object object) { }
 }

علما أن بعض التعامل مع السمة سوف تكون هناك حاجة إلا إذا كان بعض طريقة فئة يحتاج إلى اعتراضها.افتراضي جميع الطرق العامة سوف يتم اعتراضها.

نلقي نظرة على هذا - الأشياء الثقيلة..http://msdn.microsoft.com/en-us/magazine/cc164165.aspx

الأساسية .صافي - لا مربع كان الفصل على ما تحتاج يسمى اعتراض.لقد جمعت بعض من هنا (آسف عن ألوان الخط - لدي موضوع الظلام حينها...) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html

لقد وجدت طريقة مختلفة والتي قد تكون أسهل...

تعلن طريقة 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;
    }

الآن أنا يمكن أن يكون الاختيار في وقت التشغيل دون الاعتماد على حقن...

لا gotchas في الموقع :)

نأمل أنك سوف نتفق على أن هذا هو أقل وزنا ثم اوب إطار أو المستمدة من MarshalByRefObject أو استخدام الاتصال عن بعد أو الوكيل الطبقات.

أولا يجب عليك تعديل الدرجة لتنفيذ واجهة (بدلا من تنفيذ 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);
    }
}

الآن نحن على استعداد اعتراض المكالمات إلى Method1 و Method2 من ITraced

 public class Caller 
 {
     public static void Call() 
     {
         ITraced traced = (ITraced)new MethodLogInterceptor(typeof(ITraced), new Traced()).GetTransparentProxy();
         traced.Method1();
         traced.Method2(); 
     }
 }

إذا كنت تريد أن تتبع بعد أساليب دون الحد (أي رمز التكيف لا اوب إطار عدم تكرار رمز), اسمحوا لي أن أقول لكم ، تحتاج بعض السحر...

على محمل الجد ، وأصررت على تنفيذ اوب إطار العمل في وقت التشغيل.

يمكنك أن تجد هنا : NConcern .صافي الإطار اوب

قررت إنشاء هذا الإطار اوب لإعطاء الرد على هذا النوع من الاحتياجات.وهو بسيط مكتبة خفيفة الوزن جدا.يمكنك مشاهدة مثال على المسجل في الصفحة الرئيسية.

إذا كنت لا تريد استخدام 3rd الطرف الجمعية, يمكنك تصفح التعليمات البرمجية المصدر (open source) و نسخ كل الملفات الجانب.الدليل.cs و الجانب.الدليل.الدخول.cs إلى تكييفها حسب رغباتكم.أطروحات الطبقات يسمح استبدال الأساليب الخاصة بك في وقت التشغيل.أنا فقط أطلب منك أن تحترم الترخيص.

وآمل أن تجد ما تحتاج إليه أو أن يقنعك أن استخدام أخيرا اوب الإطار.

يمكنك استخدام إطار مفتوح المصدر CInject على CodePlex.يمكنك كتابة الحد الأدنى من التعليمات البرمجية لإنشاء حاقن والحصول على اعتراض أي رمز بسرعة مع CInject.بالاضافة الى ذلك, لأن هذا هو مفتوح المصدر يمكنك توسيع هذا أيضا.

أو يمكنك اتباع الخطوات المذكورة في هذه المقالة على اعتراض المكالمات الأسلوب باستخدام IL وخلق الخاصة بك اعتراضية باستخدام التفكير.تنبعث منها دروس في C#.

أنا لا أعرف الحل لكن النهج سيكون على النحو التالي.

تزيين الطبقة (أو طرق) مع سمة مخصصة.في مكان آخر في البرنامج, السماح وظيفة التهيئة تعكس جميع أنواع قراءة أساليب مزينة سمات وحقن بعض IL رمز في الأسلوب.في الواقع قد يكون أكثر عملية محل الطريقة كعب أن المكالمات LogStart, الفعلية الطريقة ثم LogEnd.بالإضافة إلى ذلك, أنا لا أعرف ما إذا كان يمكنك تغيير أساليب استخدام انعكاس لذلك قد يكون أكثر عملية استبدال كل نوع.

هل يمكن أن يحتمل استخدام صندوق القناص نمط ديكور و تزيين كل الفئات التي تحتاج إلى تتبع.

ربما فقط حقا عملي مع اللجنة الأوقيانوغرافية الحكومية الدولية حاوية (ولكن كما مؤشر في وقت سابق قد ترغب في النظر في طريقة اعتراض إذا كنت تنوي النزول اللجنة الأولمبية الدولية المسار).

تحتاج إلى علة Ayende إجابة على كيفية فعل ذلك:http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx

اوب يجب على رمز نظيفة التنفيذ ، ومع ذلك إذا كنت تريد أن تحيط كتلة في C#, generic طرق أسهل نسبيا الاستخدام.(مع ذكاء الشعور بقوة كتابة كود) ومن المؤكد أنه لا يمكن أن يكون بديلا اوب.

على الرغم من أن 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");
  1. الكتابة الخاصة بك اوب المكتبة.
  2. استخدام انعكاس لتوليد تسجيل الوكيل على حالات (لست متأكدا إذا كان يمكنك أن تفعل ذلك دون تغيير جزء من التعليمات البرمجية الموجودة).
  3. كتابة الجمعية وحقن تسجيل كود (أساسا نفس 1).
  4. المضيف CLR و إضافة قطع الأشجار في هذا المستوى (أعتقد أن هذا هو أصعب حل لتنفيذ لست متأكدا إذا كان لديك المطلوبة السنانير في CLR على الرغم من).

أفضل ما يمكنك فعله قبل C# 6 مع 'ناميوف' صدر استخدام بطيئة StackTrace و linq التعبيرات.

E. g.هذه الطريقة

    public void MyMethod(int age, string name)
    {
        log.DebugTrace(() => age, () => name);

        //do your stuff
    }

هذا الخط يمكن أن ينتج في ملف السجل الخاص بك

Method 'MyMethod' parameters age: 20 name: Mike

هنا هو التنفيذ:

    //TODO: replace with 'nameof' in C# 6
    public static void DebugTrace(this ILog log, params Expression<Func<object>>[] args)
    {
        #if DEBUG

        var method = (new StackTrace()).GetFrame(1).GetMethod();

        var parameters = new List<string>();

        foreach(var arg in args)
        {
            MemberExpression memberExpression = null;
            if (arg.Body is MemberExpression)
                memberExpression = (MemberExpression)arg.Body;

            if (arg.Body is UnaryExpression && ((UnaryExpression)arg.Body).Operand is MemberExpression)
                memberExpression = (MemberExpression)((UnaryExpression)arg.Body).Operand;

            parameters.Add(memberExpression == null ? "NA" : memberExpression.Member.Name + ": " + arg.Compile().DynamicInvoke().ToString());
        }

        log.Debug(string.Format("Method '{0}' parameters {1}", method.Name, string.Join(" ", parameters)));

        #endif
    }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top