Вопрос

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

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

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

Как мне вызвать Логгер.LogStart и Logger.LogEnd за каждый звонок в Метод1 и Метод2 без изменения Звонящий.Позвонить метод и без явного добавления вызовов в Трассируется.Метод1 и Трассируется.Метод2?

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

Это было полезно?

Решение

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

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

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

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

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

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

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

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

Другие советы

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

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

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

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

Что касается пункта № 3, ОП попросил решение без инфраструктуры АОП.В следующем ответе я предположил, что следует избегать 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) { }
 }

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

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

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

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

Объявить метод

[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 или использования удаленного взаимодействия или прокси-классов.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Хотя ПостШарп Есть небольшие проблемы с ошибками (я не уверен, что смогу использовать их в производстве), это хорошая вещь.

Общий класс-оболочка,

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. Напишите свою собственную библиотеку АОП.
  2. Используйте отражение для создания прокси-сервера журналирования для ваших экземпляров (не уверен, что вы сможете сделать это, не меняя какую-то часть существующего кода).
  3. Перепишите сборку и внедрите код регистрации (по сути, такой же, как 1).
  4. Разместите CLR и добавьте ведение журнала на этом уровне (я думаю, что это самое сложное решение для реализации, хотя я не уверен, есть ли у вас необходимые перехватчики в CLR).

Лучшее, что вы можете сделать до 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
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top