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

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

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

  • Я не хочу использовать сторонних разработчиков Библиотеки 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 для каждого вызова метода 1 и метода 2 без изменения метода Caller.Call и без явного добавления вызовов в Traced.Method1 и Traced.Method2?

Изменить: что было бы решением, если мне разрешено слегка изменить метод вызова?

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

Если вы пишете класс - вызовите его 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 ...]
            }
        }
    }

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

Вы можете достичь этого с помощью Функция перехвата в контейнере DI, например Замок Виндзор. В самом деле, можно сконфигурировать контейнер таким образом, чтобы все классы, которые имеют метод, украшенный определенным атрибутом, были перехвачены.

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

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

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

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

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

Ответ 8

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

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

Ответ 9

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

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

использование может быть таким (с понятием intelli)

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

Ответ 10

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

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

Ответ 11

Вы могли бы использовать шаблон декоратора GOF и "украсить" все классы, требующие трассировки.

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

Ответ 13

  • Напишите свою собственную библиотеку AOP.
  • Используйте рефлексию для создания прокси-сервера регистрации через свои экземпляры (не уверен, что вы можете сделать это, не изменяя часть вашего существующего кода).
  • Перепишите сборку и введите код регистрации (в основном это то же самое, что и 1).
  • Поместите CLR и добавьте ведение журнала на этом уровне (я думаю, что это самое сложное решение для реализации, но не уверен, что у вас есть необходимые перехватчики в CLR).

Ответ 14

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

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

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

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

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

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

Ответ 15

Лучшее, что вы можете сделать до того, как С# 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
    }