मैं C # में विधि कॉल को कैसे रोकूं?


154

किसी दिए गए वर्ग के लिए मैं ट्रेसिंग कार्यक्षमता रखना चाहूंगा अर्थात मैं हर विधि कॉल (विधि हस्ताक्षर और वास्तविक पैरामीटर मान) और हर विधि से बाहर निकलना चाहूंगा (बस विधि हस्ताक्षर)।

मैं यह मानकर कैसे पूरा करूं कि:

  • मैं C # के लिए किसी भी 3 पार्टी 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);
 }

मैं कैसे आह्वान करते Logger.LogStart और Logger.LogEnd के लिए हर कॉल के लिए Method1 और Method2 को संशोधित किए बिना Caller.Call विधि और करने के लिए स्पष्ट रूप से कॉल जोड़े बिना Traced.Method1 और Traced.Method2 ?

संपादित करें: यदि मैं कॉल विधि को थोड़ा बदलने की अनुमति देता हूं तो क्या समाधान होगा?



1
यदि आप जानना चाहते हैं कि C # में इंटरसेप्शन कैसे काम करता है तो टिनी इंटरसेप्टर पर एक नज़र डालें । यह नमूना बिना किसी निर्भरता के चलता है। ध्यान दें कि यदि आप वास्तविक विश्व परियोजनाओं में एओपी का उपयोग करना चाहते हैं, तो इसे स्वयं लागू करने का प्रयास न करें। PostSharp जैसे पुस्तकालयों का उपयोग करें।
जलाल

मैंने MethodDecorator.Fody लाइब्रेरी का उपयोग करके विधि कॉल (पहले और बाद) के लॉगिंग को लागू किया है। कृपया पुस्तकालय में github.com/Fody/MethodDecorator
Dilhan Jayathilake

जवाबों:


69

C # AOP ओरिएंटेड भाषा नहीं है। इसकी कुछ AOP विशेषताएं हैं और आप कुछ अन्य लोगों का अनुकरण कर सकते हैं लेकिन C # के साथ AOP बनाना दर्दनाक है।

मैं उन तरीकों की तलाश करता था जो आप करना चाहते थे और मुझे ऐसा करने का कोई आसान तरीका नहीं मिला।

जैसा कि मैं इसे समझता हूं, यह वही है जो आप करना चाहते हैं:

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

और ऐसा करने के लिए आपके पास दो मुख्य विकल्प हैं

  1. MarshalByRefObject या ContextBoundObject से अपनी कक्षा में प्रवेश करें और एक विशेषता निर्धारित करें जो IMessageSink से विरासत में मिली हो। इस लेख का एक अच्छा उदाहरण है। फिर भी आपको इस बात पर विचार करना होगा कि मार्शलीबियरऑबजेक्ट का उपयोग करने से प्रदर्शन नरक की तरह नीचे चला जाएगा, और मेरा मतलब है, मैं एक 10x प्रदर्शन के बारे में बात कर रहा हूं, ताकि कोशिश करने से पहले सावधानी से सोचें।

  2. दूसरा विकल्प कोड को सीधे इंजेक्ट करना है। रनटाइम में, जिसका अर्थ है कि आपको हर वर्ग को "पढ़ने" के लिए प्रतिबिंब का उपयोग करना होगा, इसकी विशेषताओं को प्राप्त करना होगा और एप्रीप्टिएट कॉल को इंजेक्ट करना होगा (और इस बात के लिए कि मुझे लगता है कि आप रिफ्लेक्शन का उपयोग नहीं कर सकते। मुझे लगता है कि रिफ्लेक्शन।इमिट विधि का उपयोग करें। 'आपको पहले से मौजूद पद्धति के अंदर नया कोड डालने की अनुमति देता है)। डिजाइन के समय पर इसका मतलब होगा CLR संकलक के लिए एक एक्सटेंशन बनाना जो मुझे ईमानदारी से पता नहीं है कि यह कैसे किया जाता है।

अंतिम विकल्प एक IoC ढांचे का उपयोग कर रहा है । शायद यह सही समाधान नहीं है क्योंकि अधिकांश आईओसी फ्रेमवर्क एंट्री पॉइंट्स को परिभाषित करके काम करते हैं जो तरीकों को झुकाए जाने की अनुमति देते हैं लेकिन, आप जो हासिल करना चाहते हैं, उसके आधार पर, यह एक उचित प्रतिफल हो सकता है।


62
दूसरे शब्दों में, 'ouch'
johnc

2
मुझे यह इंगित करना चाहिए कि यदि आपके पास प्रथम श्रेणी के कार्य हैं, तो एक फ़ंक्शन को किसी भी अन्य चर की तरह ही माना जा सकता है और आपके पास "विधि हुक" हो सकता है जो वह चाहता है।
RCIX

3
एक तीसरा विकल्प रनटाइम का उपयोग करके वंशानुक्रम आधारित एनओपी प्रॉक्सी को उत्पन्न करना है Reflection.Emit। यह स्प्रिंगनेट द्वारा चुना गया दृष्टिकोण है । हालांकि, इसके लिए वर्चुअल तरीकों की आवश्यकता होगी Tracedऔर IOC कंटेनर के कुछ प्रकार के बिना उपयोग के लिए वास्तव में उपयुक्त नहीं है, इसलिए मैं समझता हूं कि यह विकल्प आपकी सूची में क्यों नहीं है।
मैरिज

2
आपका दूसरा विकल्प मूल रूप से है "AOP फ्रेमवर्क के उन हिस्सों को लिखें जिन्हें आपको हाथ की जरूरत है" जिसके परिणामस्वरूप आपको "ओह प्रतीक्षा करना चाहिए" -उन्नत-यहाँ-सड़क "
Rune FS

2
@ जॉर्ज आपको डिपेंडेन्सी इंजेक्शन / आईओसी अकाल की तरह उपयोग करने के लिए इसे प्राप्त करने के लिए कुछ उदाहरण / लिंक प्रदान कर सकते हैं। जैसे
चरणराज गोल्ला

48

इसे प्राप्त करने का सबसे सरल तरीका संभवतः उपयोग करना है PostSharp । यह आपके द्वारा लागू विशेषताओं के आधार पर आपके तरीकों के अंदर कोड को इंजेक्ट करता है। यह आपको वही करने की अनुमति देता है जो आप चाहते हैं।

एक अन्य विकल्प विधि के अंदर कोड को इंजेक्ट करने के लिए प्रोफाइलिंग एपीआई का उपयोग करना है , लेकिन यह वास्तव में कट्टर है।


3
तुम भी ICorDebug के साथ सामान इंजेक्ट कर सकते हैं लेकिन यह सुपर बुराई है
सैम सैफ्रन

9

यदि आप एक क्लास लिखते हैं - इसे ट्रेसिंग कहते हैं - जो कि आइडीसॉपी इंटरफ़ेस को लागू करता है, तो आप सभी मेथड बॉडी को एक में लपेट सकते हैं

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

बहुत प्रयास की तरह दिखता है
LeRoi

3
इस सवाल का जवाब देने से कोई लेना-देना नहीं है।
लेटेंसी

9

आप इसे कैसल विंडसर जैसे डि कंटेनर के इंटरसेप्शन फ़ीचर से हासिल कर सकते हैं । वास्तव में, कंटेनर को इस तरह से कॉन्फ़िगर करना संभव है कि एक विशिष्ट विशेषता द्वारा सजाए गए प्रत्येक वर्ग को अवरोधन किया जाएगा।

बिंदु # 3 के बारे में, ओपी ने AOP ढांचे के बिना एक समाधान के लिए कहा। मैंने निम्नलिखित उत्तर में मान लिया कि क्या परहेज किया जाना चाहिए था पहलू, जॉइंटप्वाइंट, पॉइंटकॉट, आदि । कैसलविंडर से इंटरसेप्शन प्रलेखन के अनुसार , उनमें से किसी को भी पूरा करने की आवश्यकता नहीं है जो पूछा गया है।

एक विशेषता की उपस्थिति के आधार पर एक इंटरसेप्टर का सामान्य पंजीकरण कॉन्फ़िगर करें:

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) { }
 }

ध्यान दें कि विशेषता के कुछ हैंडलिंग की आवश्यकता होगी यदि किसी वर्ग की केवल कुछ विधि को बाधित करने की आवश्यकता है। डिफ़ॉल्ट रूप से, सभी सार्वजनिक तरीकों को इंटरसेप्ट किया जाएगा।


5

यदि आप बिना किसी सीमा के अपने तरीकों के बाद ट्रेस करना चाहते हैं (कोई कोड अनुकूलन, कोई AOP फ्रेमवर्क, कोई डुप्लिकेट कोड नहीं), तो मैं आपको बता दूं, आपको कुछ जादू की जरूरत है ...

गंभीरता से, मैंने इसे रनटाइम पर काम करने वाले AOP फ्रेमवर्क को लागू करने के लिए हल किया।

आप यहां पा सकते हैं: NConcern .NET .NET AOP फ्रेमवर्क

मैंने इस तरह की जरूरतों का जवाब देने के लिए इस AOP फ्रेमवर्क को बनाने का फैसला किया। यह बहुत ही हल्का एक साधारण पुस्तकालय है। आप होम पेज में लकड़हारे का एक उदाहरण देख सकते हैं।

आप विधानसभा एक 3 पार्टी का उपयोग नहीं करना चाहते, तो आप कोड स्रोत (खुला स्रोत) ब्राउज़ करें और कॉपी दोनों फ़ाइलें कर सकते हैं Aspect.Directory.cs और Aspect.Directory.Entry.cs अपनी इच्छा के रूप में रूपांतरित करने के लिए। Theses classes रनटाइम में आपके तरीकों को बदलने की अनुमति देती है। मैं आपसे केवल लाइसेंस का सम्मान करने के लिए कहूंगा।

मुझे आशा है कि आपको वह मिलेगा जो आपको चाहिए या आपको अंततः AOP फ्रेमवर्क का उपयोग करने के लिए मनाने के लिए।


4

इस पर एक नज़र डालें - बहुत भारी सामान .. http://msdn.microsoft.com/en-us/magazine/cc1641C.xx

आवश्यक .net - डॉन बॉक्स में एक अध्याय था जिसे आपको इंटरसेप्शन कहा जाता है। मैंने इसमें से कुछ को यहाँ बिखेर दिया है (फ़ॉन्ट रंग के बारे में क्षमा करें - मेरे पास तब एक गहरा विषय था ...) http://madcoderspeak.blogspot.com/2005/09/essential-interception-use-contexts.html


4

मैंने एक अलग तरीका पाया है जो आसान हो सकता है ...

एक विधि 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 से प्राप्त या रिमॉटिंग या प्रॉक्सी कक्षाओं का उपयोग करके।


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(); 
     }
 }

2

आप ओपन सोर्स फ्रेमवर्क CInject का उपयोग कर सकते हैं CodePlex पर । आप एक इंजेक्टर बनाने के लिए न्यूनतम कोड लिख सकते हैं और इसे CInject के साथ किसी भी कोड को जल्दी से इंटरसेप्ट करने के लिए प्राप्त कर सकते हैं। साथ ही, चूंकि यह ओपन सोर्स है इसलिए आप इसे बढ़ा भी सकते हैं।

या आप IL का उपयोग करके इंटरसेप्टिंग मेथड कॉल्स पर इस लेख में वर्णित चरणों का पालन कर सकते हैं और C # में Reflection.Emit कक्षाओं का उपयोग करके अपने स्वयं के इंटरसेप्टर बना सकते हैं।


1

मैं एक समाधान नहीं जानता, लेकिन मेरा दृष्टिकोण इस प्रकार होगा।

कस्टम विशेषता के साथ वर्ग (या इसके तरीकों) को सजाने। कार्यक्रम में कहीं और, एक आरंभीकरण फ़ंक्शन को सभी प्रकारों को प्रतिबिंबित करने दें, विशेषताओं के साथ सजाए गए तरीकों को पढ़ें और कुछ IL कोड को विधि में इंजेक्ट करें। यह वास्तव में स्टब द्वारा विधि को प्रतिस्थापित करने के लिए अधिक व्यावहारिक हो सकता है जो कॉल करता है LogStart, वास्तविक विधि और फिर LogEnd। इसके अतिरिक्त, मुझे नहीं पता कि आप प्रतिबिंब का उपयोग करके तरीके बदल सकते हैं, इसलिए पूरे प्रकार को बदलने के लिए यह अधिक व्यावहारिक हो सकता है।


1

आप संभावित रूप से GOF डेकोरेटर पैटर्न का उपयोग कर सकते हैं, और उन सभी वर्गों को 'सजा' सकते हैं जिन्हें ट्रेसिंग की आवश्यकता है।

यह शायद केवल एक IOC कंटेनर के साथ वास्तव में व्यावहारिक है (लेकिन जैसा कि पहले संकेतकर्ता आप विधि अवरोधन पर विचार करना चाह सकते हैं यदि आप IOC पथ से नीचे जा रहे हैं)।



1

AOP स्वच्छ कोड कार्यान्वयन के लिए आवश्यक है, हालाँकि यदि आप C # में किसी ब्लॉक को घेरना चाहते हैं, तो जेनेरिक विधियों का अपेक्षाकृत आसान उपयोग होता है। (इंटेली सेन्स और दृढ़ता से टाइप किए गए कोड के साथ) निश्चित रूप से, यह एओपी के लिए एक विकल्प नहीं हो सकता है।

यद्यपि पोस्टशार्प में छोटी छोटी समस्याएं हैं (मैं उत्पादन में उपयोग करने के लिए आश्वस्त नहीं हूं), यह एक अच्छा सामान है।

सामान्य आवरण वर्ग,

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");

मैं मानता हूं कि Postharp एक AOP लाइब्रेरी है और इंटरसेप्शन को हैंडल करेगा, हालांकि आपका उदाहरण इस प्रकार के कुछ भी नहीं दिखाता है। अवरोधन के साथ IoC को भ्रमित न करें। वे एक जैसे नहीं हैं।
विलंबता

-1
  1. अपनी खुद की AOP लाइब्रेरी लिखें।
  2. अपने उदाहरणों पर लॉगिंग प्रॉक्सी बनाने के लिए प्रतिबिंब का उपयोग करें (यदि आप अपने मौजूदा कोड के कुछ हिस्से को बदले बिना ऐसा कर सकते हैं तो निश्चित नहीं)।
  3. असेंबली को फिर से लिखें और अपने लॉगिंग कोड (मूल रूप से 1 के समान) को इंजेक्ट करें।
  4. सीएलआर की मेजबानी करें और इस स्तर पर लॉगिंग जोड़ें (मुझे लगता है कि यह लागू करने के लिए सबसे मुश्किल समाधान है, यह सुनिश्चित नहीं है कि आपके पास सीएलआर में आवश्यक हुक हैं हालांकि)।

-3

रिलीज़ किए गए 'नेमॉफ़' के साथ C # 6 से पहले आप जो सबसे अच्छा कर सकते हैं, वह है स्टैकट्रेस और लाइनक एक्सप्रेशंस का उपयोग करना।

इस तरह की विधि के लिए

    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
    }

यह उसकी दूसरी गोली बिंदु में परिभाषित आवश्यकता को विफल करता है।
टेड बिगहम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.