Как перехватить вызов метода в C #?

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

Как мне сделать это, предполагая, что:

  • Я не хочу использовать сторонние библиотеки 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?

25.08.2008 09:00:23
George Duckett 10.06.2013 11:48:55
Если вы хотите узнать, как работает перехват в C #, взгляните на Tiny Interceptor . Этот образец работает без каких-либо зависимостей. Обратите внимание: если вы хотите использовать AOP в реальных проектах, не пытайтесь реализовать это самостоятельно. Используйте библиотеки, такие как PostSharp.
Jalal 25.07.2016 18:58:53
Я реализовал ведение журнала вызова метода (до и после) с использованием библиотеки MethodDecorator.Fody. Пожалуйста, взгляните на библиотеку на github.com/Fody/MethodDecorator
Dilhan Jayathilake 19.01.2017 00:38:46
15 ОТВЕТОВ
РЕШЕНИЕ

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 . Возможно, это не идеальное решение, так как большинство IoC-фреймворков работают, определяя точки входа, которые позволяют подключать методы, но, в зависимости от того, чего вы хотите достичь, это может быть справедливым приближением.

69
25.08.2008 09:31:40
Другими словами, «ой»
johnc 29.12.2008 04:23:25
Я должен отметить, что если бы у вас были функции первого класса, то функцию можно было бы обрабатывать так же, как любую другую переменную, и вы могли бы иметь «метод-хук», который делает то, что он хочет.
RCIX 26.08.2009 04:18:13
Третий вариант - генерировать aop-прокси на основе наследования во время выполнения с использованием Reflection.Emit. Это подход, выбранный Spring.NET . Однако для этого потребуются виртуальные методы, Tracedи они не очень подходят для использования без какого-либо контейнера IOC, поэтому я понимаю, почему этот параметр отсутствует в вашем списке.
Marijn 18.11.2011 10:39:57
ваш второй вариант в основном «напишите необходимые части AOP-фреймворка вручную», что должно привести к выводу «Ой, подождите, может быть, я должен использовать сторонний вариант, созданный специально для решения проблемы, которая у меня есть, вместо того, чтобы отказываться от -inveted-here-road "
Rune FS 9.08.2013 09:42:47
@jorge Можете ли вы предоставить пример / ссылку для достижения этой цели с помощью Dependency Injection / IoC, например nInject
Charanraj Golla 3.10.2013 01:30:39
  1. Напишите свою собственную библиотеку АОП.
  2. Используйте отражение, чтобы сгенерировать прокси-сервер журналирования для ваших экземпляров (не уверен, что вы можете сделать это без изменения какой-либо части существующего кода).
  3. Перепишите сборку и введите код регистрации (в основном такой же, как 1).
  4. Разместите CLR и добавьте протоколирование на этом уровне (я думаю, что это самое сложное решение для реализации, хотя я не уверен, что у вас есть необходимые хуки в CLR).
-1
25.08.2008 09:06:09

Я не знаю решения, но мой подход был бы следующим.

Украсьте класс (или его методы) пользовательским атрибутом. Где-то еще в программе, пусть функция инициализации отражает все типы, читает методы, украшенные атрибутами, и вводит некоторый код IL в метод. На самом деле может быть более практичным заменить метод на заглушку, которая вызывает LogStartметод, а затем на метод LogEnd. Кроме того, я не знаю, можете ли вы изменить методы, используя отражение, так что может быть более практичным заменить весь тип.

1
25.08.2008 09:07:28

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

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 ...]
            }
        }
    }
10
25.08.2008 09:12:02
Похоже, много усилий
LeRoi 10.01.2018 14:26:27
Это не имеет ничего общего с ответом на вопрос.
Latency 12.03.2019 20:34:47

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

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

4
25.08.2008 09:12:17

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

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

48
21.02.2017 21:24:30
Вы также можете вводить вещи с помощью ICorDebug, но это супер зло
Sam Saffron 7.05.2009 09:30:28

Вы можете использовать шаблон GOF Decorator и «декорировать» все классы, которые нуждаются в трассировке.

Вероятно, это действительно практично только с контейнером IOC (но, как указывалось ранее, вы можете рассмотреть возможность перехвата метода, если собираетесь идти по пути IOC).

1
18.09.2008 16:31:19

вам нужно уговорить Айенде за ответ о том, как он это сделал: http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx

1
21.11.2009 06:34:06

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

Объявите метод 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
17.03.2011 20:45:17

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");
1
28.06.2011 15:30:48
Я согласен, что Postsharp является библиотекой AOP и будет обрабатывать перехват, однако ваш пример не иллюстрирует ничего подобного. Не путайте IoC с перехватом. Они не одинаковы.
Latency 12.03.2019 20:40:38

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

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

2
11.12.2012 02:34:04

Лучшее, что вы можете сделать до выхода C # 6 с именем nameof - это использовать медленные выражения StackTrace и linq.

Например для такого метода

    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
    }
-3
12.07.2015 03:44:07
Это не соответствует требованию, определенному в его втором пункте.
Ted Bigham 28.10.2015 22:34:19

Сначала вы должны изменить свой класс для реализации интерфейса (а не реализации 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
19.12.2015 14:27:46

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

Что касается пункта № 3, OP запросил решение без AOP Framework. В следующем ответе я предположил, что следует избегать Aspect, 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");
    }
}

Добавьте атрибут logging в ваш метод для регистрации

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

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

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

8
13.12.2016 19:07:24

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

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

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

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

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

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

5
13.12.2016 20:42:14