Сведения о вопросе

Life

08:44, 22nd August, 2020

Теги

c#   reflection   aop    

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

Просмотров: 518   Ответов: 15

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

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

  • Я не хочу использовать какую-либо третью сторону AOP библиотеки для C#,
  • Я не хочу добавлять дубликат кода ко всем методам, которые я хочу trace,
  • Я не хочу изменять public 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 ?

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



  Сведения об ответе

repe

01:52, 20th August, 2020

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

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

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

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

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

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

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

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


  Сведения об ответе

LIZA

15:43, 28th August, 2020

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

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


  Сведения об ответе

darknet

19:18, 8th August, 2020

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

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

В классе трассировки вы можете обрабатывать логику трассировок в методе конструктора / Dispose, соответственно, в классе трассировки, чтобы отслеживать вход и выход методов. Такое, что:

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


  Сведения об ответе

#hash

13:53, 25th August, 2020

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

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

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


  Сведения об ответе

prince

00:10, 20th August, 2020

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

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


  Сведения об ответе

davran

03:42, 14th August, 2020

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

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


  Сведения об ответе

DAAA

23:25, 24th August, 2020

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


  Сведения об ответе

PIRLO

07:41, 3rd August, 2020

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

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

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

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

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

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


  Сведения об ответе

PAGE

17:09, 7th August, 2020

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

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


  Сведения об ответе

PIRLO

02:25, 6th August, 2020

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


  Сведения об ответе

ITSME

04:44, 9th August, 2020

AOP является обязательным для реализации чистого кода, однако если вы хотите окружить блок в C#, универсальные методы имеют относительно более простое использование. (с помощью intelli sense и строго типизированного кода) конечно, это может быть NOT альтернативой AOP.

Хотя у 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 sense, конечно)

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


  Сведения об ответе

DAAA

18:01, 29th August, 2020

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

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


  Сведения об ответе

davran

20:32, 24th August, 2020

Вы можете потенциально использовать шаблон декоратора GOF и 'decorate' всех классов, которые нуждаются в трассировке.

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


  Сведения об ответе

dumai

04:54, 11th August, 2020

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


  Сведения об ответе

park

19:50, 12th August, 2020

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

E.g. для такого способа

    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
    }


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться