Как перехватить вызов метода в C #? - PullRequest
144 голосов
/ 25 августа 2008

Для данного класса я хотел бы иметь функцию трассировки, т.е. я хотел бы регистрировать каждый вызов метода (сигнатура метода и фактические значения параметров) и каждый выход метода (только сигнатура метода).

Как мне это сделать, если:

  • Я не хочу использовать третьи лица AOP библиотеки для 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);
 }

Как вызвать Logger.LogStart и Logger.LogEnd для каждого вызова Method1 и Method2 без изменения Caller.Call и без явного добавления вызовов в Traced.Method1 и Traced.Method2 ?

Редактировать: Каким было бы решение, если бы мне позволили немного изменить метод Call?

Ответы [ 15 ]

66 голосов
/ 25 августа 2008

C # не является языком, ориентированным на AOP. Он имеет некоторые функции AOP, и вы можете эмулировать некоторые другие, но создание AOP с C # является болезненным.

Я искал способы сделать именно то, что вы хотели, и я не нашел простого способа сделать это.

Насколько я понимаю, это то, что вы хотите сделать:

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

и для этого у вас есть два основных варианта

  1. Унаследуйте ваш класс от MarshalByRefObject или ContextBoundObject и определите атрибут, который наследуется от IMessageSink. В этой статье есть хороший пример. Тем не менее, вы должны учитывать, что при использовании MarshalByRefObject производительность будет падать до чертиков, и я имею в виду, я говорю о 10-кратной потере производительности, поэтому подумайте внимательно, прежде чем пытаться это сделать.

  2. Другой вариант - ввести код напрямую. Во время выполнения это означает, что вам придется использовать отражение, чтобы «прочитать» каждый класс, получить его атрибуты и ввести соответствующий вызов (и в этом отношении я думаю, что вы не можете использовать метод Reflection.Emit, так как я думаю, что Reflection.Emit не позволяет вставлять новый код в уже существующий метод). Во время разработки это будет означать создание расширения для компилятора CLR, которое я, честно говоря, понятия не имею, как это делается.

Последний вариант - использование IoC framework . Возможно, это не идеальное решение, так как большинство IoC-фреймворков работают путем определения точек входа, которые позволяют подключать методы, но, в зависимости от того, чего вы хотите достичь, это может быть справедливым приближением.

45 голосов
/ 25 августа 2008

Самый простой способ достичь этого - использовать PostSharp . Он внедряет код в ваши методы на основе атрибутов, которые вы к нему применяете. Это позволяет вам делать именно то, что вы хотите.

Другой вариант - использовать API профилирования для внедрения кода внутри метода, но это действительно хардкор.

10 голосов
/ 25 августа 2008

Если вы напишите класс - назовите его Tracing - который реализует интерфейс IDisposable, вы можете заключить все тела методов в

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

В классе Tracing вы можете обрабатывать логику трасс в методе constructor / 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 ...]
            }
        }
    }
5 голосов
/ 13 декабря 2016

Этого можно добиться с помощью функции Перехват контейнера DI, например Castle Windsor . Действительно, можно настроить контейнер таким образом, чтобы все классы, у которых есть метод, декорированный определенным атрибутом, были бы перехвачены.

Что касается пункта № 3, OP запросил решение без AOP-фреймворка. В следующем ответе я предположил, что следует избегать Aspect, JointPoint, PointCut и т.д.

Настройка общей регистрации перехватчика на основе наличия атрибута:

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

Добавьте атрибут logging к вашему методу в log

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

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

Обратите внимание, что некоторая обработка атрибута потребуется, если требуется перехватить только какой-либо метод класса. По умолчанию все открытые методы будут перехвачены.

4 голосов
/ 13 декабря 2016

Если вы хотите отслеживать ваши методы без ограничений (без адаптации кода, без AOP Framework, без дублированного кода), позвольте мне сказать вам, вам нужно немного магии ...

Серьезно, я решил внедрить AOP Framework, работающий во время выполнения.

Вы можете найти здесь: NConcern .NET AOP Framework

Я решил создать эту AOP Framework, чтобы дать ответ на такого рода потребности. это простая библиотека, очень легкая. Вы можете увидеть пример регистратора на домашней странице.

Если вы не хотите использовать стороннюю сборку, вы можете просмотреть исходный код (с открытым исходным кодом) и скопировать оба файла Aspect.Directory.cs и Aspect.Directory.Entry .cs адаптировано под ваши пожелания. Эти классы позволяют заменять ваши методы во время выполнения. Я бы просто попросил вас уважать лицензию.

Надеюсь, вы найдете то, что вам нужно, или убедите вас наконец использовать AOP Framework.

4 голосов
/ 27 августа 2015

Сначала вы должны изменить свой класс для реализации интерфейса (а не реализации 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(); 
     }
 }
4 голосов
/ 17 марта 2011

Я нашел другой способ, который может быть проще ...

Объявление метода 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 Framework, либо наследуется от MarshalByRefObject, либо использует удаленные или прокси-классы.

4 голосов
/ 25 августа 2008

Взгляните на это - довольно тяжелые вещи .. http://msdn.microsoft.com/en-us/magazine/cc164165.aspx

Essential .net - don box содержит главу о том, что вам нужно, называется Перехват. Я кое-что здесь почистил (извините за цвета шрифта - тогда у меня была темная тема ...) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html

2 голосов
/ 11 декабря 2012

Вы можете использовать инфраструктуру с открытым исходным кодом CInject в CodePlex. Вы можете написать минимальный код для создания Инжектора и заставить его быстро перехватывать любой код с помощью CInject. Кроме того, поскольку это Open Source, вы можете расширить это.

Или вы можете выполнить шаги, упомянутые в этой статье на Перехват вызовов методов с использованием IL и создать свой собственный перехватчик с использованием классов Reflection.Emit в C #.

1 голос
/ 13 мая 2011

AOP является обязательным условием для реализации чистого кода, однако, если вы хотите заключить блок в C #, универсальные методы имеют относительно более простое использование. (с интеллигентным смыслом и строго типизированным кодом) Конечно, это НЕ может быть альтернативой для АОП.

Хотя 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");
...