C # yöntem çağrısı nasıl keserim?


154

Belirli bir sınıf için izleme işlevselliği istiyorum yani her yöntem çağrısı (yöntem imzası ve gerçek parametre değerleri) ve her yöntem çıkışı (sadece yöntem imzası) günlüğe kaydetmek istiyorum.

Bunu varsayarak bunu nasıl başarabilirim:

  • C # için herhangi bir 3. taraf AOP kütüphanesi kullanmak istemiyorum,
  • İzlemek istediğim tüm yöntemlere yinelenen kod eklemek istemiyorum,
  • Sınıfın genel API'sını değiştirmek istemiyorum - sınıfın kullanıcıları tüm yöntemleri tam olarak aynı şekilde çağırabilmelidir.

Soruyu daha somut hale getirmek için 3 sınıf olduğunu varsayalım:

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

Nasıl çağırmak yapmak Logger.LogStart ve Logger.LogEnd her çağrı için Method1 ve Method2 değiştirmeden Caller.Call yöntemi ve açıkça aramaları eklemeden Traced.Method1 ve Traced.Method2 ?

Düzenleme: Çağrı yöntemini hafifçe değiştirebilirsem çözüm ne olur?



1
C # 'da müdahalenin nasıl çalıştığını bilmek istiyorsanız Tiny Interceptor'a bir göz atın . Bu örnek herhangi bir bağımlılık olmadan çalışır. AOP'yi gerçek dünya projelerinde kullanmak istiyorsanız, bunu kendiniz uygulamaya çalışmayın. PostSharp gibi kütüphaneleri kullanın.
Jalal

MethodDecorator.Fody kitaplığını kullanarak (önce ve sonra) bir yöntem çağrısı günlüğe kaydetme uygulamak var. Lütfen github.com/Fody/MethodDecorator
Dilhan Jayathilake

Yanıtlar:


69

C #, AOP yönelimli bir dil değildir. Bazı AOP özellikleri vardır ve bazılarını taklit edebilirsiniz ancak C # ile AOP yapmak acı vericidir.

Tam olarak ne yapmak istediğinizi yapmanın yollarını aradım ve bunu yapmanın kolay bir yolunu bulamadım.

Anladığım kadarıyla, yapmak istediğiniz budur:

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

ve bunu yapabilmek için iki ana seçeneğiniz var

  1. Sınıfınızı MarshalByRefObject veya ContextBoundObject öğesinden devralın ve IMessageSink öğesinden devralınan bir öznitelik tanımlayın. Bu makalenin güzel bir örneği var. Bununla birlikte, bir MarshalByRefObject kullanarak performansın cehennem gibi düşeceğini düşünmelisiniz ve demek istediğim, 10x performans kaybından bahsediyorum, bunu denemeden önce dikkatlice düşünün.

  2. Diğer seçenek kodu doğrudan enjekte etmektir. Çalışma zamanında, yani her sınıfı "okumak", özniteliklerini almak ve uygun çağrıyı enjekte etmek (ve bu nedenle sanırım Reflection.Emit yöntemini düşündüğüm gibi Reflection.Emit yöntemini kullanamazsınız. zaten mevcut bir yöntemin içine yeni kod eklemenize izin vermez). Tasarım zamanında bu, nasıl yapıldığına dair hiçbir fikrim yok CLR derleyicisine bir uzantı oluşturmak anlamına gelecektir.

Son seçenek bir IoC çerçevesi kullanmaktır . Çoğu IoC çerçevesi, yöntemlerin takılmasına izin veren giriş noktalarını tanımlayarak çalıştığı için mükemmel bir çözüm değildir, ancak elde etmek istediğiniz şeye bağlı olarak, bu adil bir yakınlık olabilir.


62
Başka bir deyişle, 'ah'
johnc

2
Birinci sınıf işlevleriniz varsa, o zaman bir işlev diğer değişkenler gibi ele alınabileceğini ve istediği şeyi yapan bir "yöntem kancası" olabileceğini belirtmeliyim.
RCIX

3
Üçüncü bir alternatif, çalışma zamanında kalıtım tabanlı bir aop vekilleri oluşturmaktır Reflection.Emit. Bu, Spring.NET tarafından seçilen yaklaşımdır . Ancak, bu sanal yöntemler gerektirir Tracedve bir çeşit IOC kapsayıcısı olmadan kullanmak için gerçekten uygun değildir, bu yüzden bu seçeneğin neden listenizde olmadığını anlıyorum.
Marijn

2
ikinci seçeneğiniz temelde "AOP çerçevesinin elinizdeki kısımlarını elinizle yazın". -inveted-here-road "
Rune FS

2
@jorge nInject gibi Bağımlılık Enjeksiyonu / IoC famework kullanarak bunu gerçekleştirmek için bir örnek / bağlantı sağlayabilir misiniz
Charanraj Golla 3:30 '

48

Bunu başarmanın en basit yolu muhtemelen PostSharp kullanmaktır . Uyguladığınız özelliklere göre yöntemlerinize kod ekler. Tam olarak ne istediğinizi yapmanızı sağlar.

Başka bir seçenek de , yöntemin içine kod enjekte etmek için profil oluşturma API'sını kullanmaktır , ancak bu gerçekten hardcoredur.


3
Ayrıca ICorDebug ile bir şeyler enjekte edebilirsiniz ama bu çok kötü
Sam Saffron

9

IDisposable arabirimini uygulayan bir sınıf (İzleme olarak adlandırın) yazarsanız, tüm yöntem gövdelerini bir

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

Tracing sınıfında, yöntemlerin giriş ve çıkışlarını takip etmek için Tracing sınıfında sırasıyla yapıcı / Dispose yöntemindeki izlerin mantığını işleyebilirsiniz. Öyle ki:

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

çok çaba gibi görünüyor
LeRoi

3
Bunun soruyu cevaplamakla bir ilgisi yok.
Gecikme

9

Windsor Kalesi gibi bir DI konteynerinin Interception özelliği ile elde edebilirsiniz. . Aslında, kapsayıcıyı, belirli bir öznitelik tarafından dekore edilmiş bir yöntemi olan her sınıfın ele geçirileceği şekilde yapılandırmak mümkündür.

3. nokta ile ilgili olarak OP, AOP çerçevesi olmayan bir çözüm istedi. Aşağıdaki cevapta kaçınılması gereken şeyin Aspect, JointPoint, PointCut, vb . Olduğunu varsaydım. CastleWindsor Interception belgelerine göre , bunların hiçbirini istenen şeyi gerçekleştirmek için gerekli değildir.

Bir Interceptor'ın genel kaydını, bir özelliğin varlığına göre yapılandırın:

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

Oluşturulan IContributeComponentModelConstruction'ı kaba ekleyin

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

Ve önleyicinin kendisinde ne istersen yapabilirsin

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

Günlüğe kaydetme yönteminize günlüğe kaydetme özelliğini ekleyin

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

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

Bir sınıfın yalnızca bazı yöntemlerinin ele geçirilmesi gerekiyorsa, niteliğin bir miktar işlenmesinin gerekli olacağını unutmayın. Varsayılan olarak, tüm genel yöntemler ele geçirilir.


5

Yöntemlerinizden sonra sınırsız olarak izlemek istiyorsanız (kod uyarlaması yok, AOP Framework yok, yinelenen kod yok), size söyleyeyim, biraz sihire ihtiyacınız var ...

Cidden, çalışma zamanında çalışan bir AOP Çerçevesi uygulamak için çözdüm.

Burada bulabilirsiniz: NConcern .NET AOP Çerçevesi

Bu tür ihtiyaçlara cevap vermek için bu AOP Çerçevesini oluşturmaya karar verdim. çok hafif basit bir kütüphane. Giriş sayfasında bir günlükçü örneği görebilirsiniz.

3. taraf bir montaj kullanmak istemiyorsanız, kod kaynağına (açık kaynak) göz atabilir ve Aspect.Directory.cs ve Aspect.Directory.Entry.cs dosyalarını istediğiniz gibi uyarlayabilirsiniz. Bu sınıflar çalışma zamanında yöntemlerinizin değiştirilmesine izin verir. Sizden sadece lisansa saygı duymanızı rica ediyorum.

Umarım ihtiyacınız olanı bulur veya sizi sonunda bir AOP Çerçevesi kullanmaya ikna edersiniz.



4

Daha kolay olabilecek farklı bir yol buldum ...

InvokeMethod Yöntemi Bildirme

[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;
        }
    }

Sonra yöntemlerimi şöyle tanımlıyorum

[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;
    }

Şimdi bağımlılık enjeksiyonu olmadan çalışma zamanında kontrol yapabilirim ...

Site yok got :)

Umarım bunun bir AOP Çerçevesinden veya MarshalByRefObject'ten veya uzak veya proxy sınıflarını kullanarak türetmekten daha az ağırlık olduğunu kabul edersiniz.


4

İlk önce bir arabirimi uygulamak için sınıfınızı değiştirmeniz gerekir (MarshalByRefObject yerine).

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

Ardından, dekore edilmiş nesneye yapılan herhangi bir çağrının kesilmesine izin vermek için herhangi bir arabirimi süslemek için RealProxy tabanlı genel bir sarma nesnesine ihtiyacınız vardır.

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

Artık ITraced'ın Method1 ve Method2 çağrılarına müdahale etmeye hazırız

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

2

Açık kaynak çerçeveli CInject kullanabilirsinizCodePlex üzerinde . Enjektör oluşturmak için minimum kod yazabilir ve CInject ile herhangi bir kodu hızlı bir şekilde yakalamasını sağlayabilirsiniz. Ayrıca, bu Açık Kaynak olduğundan bunu da genişletebilirsiniz.

Ya da IL kullanarak yöntem çağrılarını engelleme hakkında bu makalede belirtilen adımları izleyebilir ve C # 'de sınıfları yansıtan Reflection.Emit kullanarak kendi önleyici oluşturun.


1

Bir çözüm bilmiyorum ama yaklaşımım aşağıdaki gibi olurdu.

Sınıfı (veya yöntemlerini) özel bir nitelikle süsleyin. Programın başka bir yerinde, bir başlatma işlevinin tüm türleri yansıtmasına izin verin, özniteliklerle süslenmiş yöntemleri okuyun ve yönteme bazı IL kodları enjekte edin. Yöntemi, çağıran bir saplama , gerçek yöntem ve daha sonra değiştirmek daha pratik olabilir . Buna ek olarak, yansıma kullanarak yöntemleri değiştirip değiştiremeyeceğinizi bilmiyorum, bu nedenle tüm türün yerine koymak daha pratik olabilir.LogStartLogEnd


1

GOF Dekoratör Desenini potansiyel olarak kullanabilir ve izlemeye ihtiyaç duyan tüm sınıfları 'süsleyebilirsiniz'.

Muhtemelen sadece bir IOC kapsayıcısı ile gerçekten pratiktir (ancak daha önce işaretçi olarak IOC yoluna gidecekseniz yöntem müdahalesini düşünmek isteyebilirsiniz).



1

AOP, temiz kod uygulaması için bir zorunluluktur, ancak C # 'da bir bloğu çevrelemek istiyorsanız, genel yöntemlerin kullanımı nispeten daha kolaydır. (akıllıca ve güçlü bir şekilde yazılmış kod ile) Kesinlikle, AOP için bir alternatif OLAMAZ.

PostSHarp rağmen küçük buggy sorunları (üretimde kullanmaktan emin değilim), iyi bir şey.

Genel ambalaj sınıfı,

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

kullanımı böyle olabilir (akıllıca bir anlamda)

var exception = Wrapper.LogOnError(() =>
{
  MessageBox.Show("test");
  throw new Exception("test");
}, "Hata");

Postsharp'ın bir AOP kütüphanesi olduğunu ve müdahaleyi ele alacağını kabul ediyorum, ancak örneğiniz bu türden hiçbir şeyi göstermiyor. IoC'yi müdahale ile karıştırmayın. Aynı değiller.
Gecikme

-1
  1. Kendi AOP kütüphanenizi yazın.
  2. Örnekleriniz üzerinde bir günlük proxy oluşturmak için yansımayı kullanın (mevcut kodunuzun bir kısmını değiştirmeden bunu yapıp yapamayacağınızdan emin değilsiniz).
  3. Montajı yeniden yazın ve günlük kodunuzu enjekte edin (temel olarak 1 ile aynıdır).
  4. CLR ana ve bu düzeyde günlük ekleme (bu uygulamak için en zor çözüm olduğunu düşünüyorum, CLR gerekli kanca olsa emin değilim).

-3

'Nameof' ile C # 6'dan önce yapabileceğiniz en iyi şey yavaş StackTrace ve linq İfadeleri kullanmaktır.

Örneğin bu yöntem için

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

        //do your stuff
    }

Böyle bir çizgi günlük dosyanızda üretilebilir

Method 'MyMethod' parameters age: 20 name: Mike

İşte uygulama:

    //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
    }

Bu, ikinci madde işaret noktasında tanımlanan gereksinimi ortadan kaldırır.
Ted Bigham
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.