Для данного класса я хотел бы иметь функцию трассировки, т.е. я хотел бы регистрировать каждый вызов метода (подпись метода и фактические значения параметров) и каждый выход метода (только подпись метода).
Как мне это сделать, если:
Чтобы сделать вопрос более конкретным, предположим, что существует 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?
Обновлено: каково было бы решение, если бы мне разрешили немного изменить метод вызова?
Если вы хотите узнать, как работает перехват в C#, взгляните на Крошечный перехватчик. Этот образец работает без каких-либо зависимостей. Обратите внимание: если вы хотите использовать АОП в реальных проектах, не пытайтесь реализовать его самостоятельно. Используйте библиотеки, такие как PostSharp.
Я реализовал ведение журнала вызова метода (до и после) с использованием библиотеки MethodDecorator.Fody. Пожалуйста, посмотрите библиотеку на github.com/Fody/MethodDecorator





Я не знаю решения, но мой подход будет следующим.
Украсьте класс (или его методы) настраиваемым атрибутом. В другом месте программы позвольте функции инициализации отразить все типы, прочитать методы, украшенные атрибутами, и внедрить некоторый код IL в метод. На самом деле может быть более практичным заменять метод с помощью заглушки, которая вызывает LogStart, фактический метод, а затем LogEnd. Кроме того, я не знаю, можно ли изменить методы с помощью отражения, поэтому было бы более практично заменить весь тип.
Если вы напишете класс - назовите его 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 ...]
}
}
}
похоже, много усилий
Это не имеет ничего общего с ответом на вопрос.
Это называется «копипаста».
Взгляните на это - довольно тяжелая штука .. http://msdn.microsoft.com/en-us/magazine/cc164165.aspx
Essential .net - в коробке Don была глава о том, что вам нужно, под названием Перехват. Я поскреб кое-что здесь (извините за цвета шрифта - у меня тогда была темная тема ...) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html
C# не является языком, ориентированным на АОП. В нем есть некоторые функции АОП, и вы можете имитировать некоторые другие, но создание АОП с помощью C# болезненно.
Я искал способы сделать именно то, что вы хотели сделать, и не нашел легкого способа сделать это.
Насколько я понимаю, это то, что вы хотите сделать:
[Log()]
public void Method1(String name, Int32 value);
и для этого у вас есть два основных варианта
Наследуйте свой класс от MarshalByRefObject или ContextBoundObject и определите атрибут, который наследуется от IMessageSink. У Эта статья есть хороший пример. Тем не менее, вы должны учитывать, что при использовании MarshalByRefObject производительность будет падать как ад, и я имею в виду это, я говорю о 10-кратной потере производительности, поэтому хорошо подумайте, прежде чем пытаться это сделать.
Другой вариант - напрямую ввести код. Во время выполнения, это означает, что вам придется использовать отражение, чтобы «читать» каждый класс, получать его атрибуты и вводить соответствующий вызов (и в этом отношении я думаю, что вы не могли бы использовать метод Reflection.Emit, поскольку я думаю, что Reflection.Emit не будет 'не позволяют вставлять новый код в уже существующий метод). Во время разработки это будет означать создание расширения для компилятора CLR, о котором я, честно говоря, понятия не имею, как это делается.
Последний вариант - использование Фреймворк IoC. Возможно, это не идеальное решение, поскольку большинство фреймворков IoC работают, определяя точки входа, которые позволяют подключать методы, но, в зависимости от того, чего вы хотите достичь, это может быть справедливым приближением.
Я должен отметить, что если бы у вас были функции первого класса, то с функцией можно было бы обращаться так же, как с любой другой переменной, и у вас мог бы быть «ловушка метода», которая делает то, что он хочет.
Третья альтернатива - сгенерировать прокси-серверы aop на основе наследования во время выполнения с использованием Reflection.Emit. Это подход, выбранный Spring.NET. Однако для этого потребуются виртуальные методы на Traced, и это не совсем подходит для использования без какого-либо контейнера IOC, поэтому я понимаю, почему этой опции нет в вашем списке.
ваш второй вариант - это в основном «Напишите части фреймворка АОП, которые вам нужны вручную», что затем должно привести к выводу: «Ой, подождите, возможно, мне следует использовать сторонний вариант, созданный специально для решения моей проблемы, вместо того, чтобы -ввел-здесь-дорога »
@jorge Можете ли вы предоставить какой-нибудь пример / ссылку для достижения этого с помощью структуры Dependency Injection / IoC, такой как nInject
Получил здесь, перехват с использованием внедрения зависимостей: stackoverflow.com/questions/12083052/…
ContextBoundObject слишком медленный, 100x
Разве "АОП" не плеоназм?
@Charanraj: Я добавил один ниже, полученный с помощью Castle Windsor: stackoverflow.com/a/41128743/1180998
Просто используйте фреймворк IoC. Практически все они имеют паттерны АОП на основе перехватчиков.
Самый простой способ добиться этого - использовать PostSharp. Он внедряет код в ваши методы на основе атрибутов, которые вы к нему применяете. Это позволяет делать именно то, что вы хотите.
Другой вариант - использовать профилирование API для внедрения кода внутри метода, но это действительно хардкор.
вы также можете вводить что-то с помощью ICorDebug, но это супер зло
Вы могли бы потенциально использовать шаблон декоратора GOF и «украсить» все классы, которые нуждаются в трассировке.
Это, вероятно, действительно практично только с контейнером IOC (но, как указывалось ранее, вы можете рассмотреть возможность перехвата метода, если вы собираетесь пойти по пути IOC).
вам нужно жучить Айенде, чтобы узнать, как он это сделал: http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx
Я нашел другой способ, который может быть проще ...
Объявление метода 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, использования удаленного взаимодействия или прокси-классов.
АОП является обязательным условием реализации чистого кода, однако, если вы хотите окружить блок в 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);
}
});
}
}
использование могло быть таким (конечно, с разумом intelli)
var exception = Wrapper.LogOnError(() =>
{
MessageBox.Show("test");
throw new Exception("test");
}, "Hata");
Я согласен с тем, что Postsharp является библиотекой АОП и будет обрабатывать перехват, однако ваш пример ничего подобного не иллюстрирует. Не путайте IoC с перехватом. Они не то же самое.
Вы можете использовать фреймворк CInject с открытым исходным кодом на CodePlex. Вы можете написать минимальный код для создания инжектора и заставить его быстро перехватывать любой код с помощью CInject. Кроме того, поскольку это открытый исходный код, вы также можете расширить его.
Или вы можете выполнить шаги, упомянутые в этой статье о Перехват вызовов методов с использованием IL, и создать свой собственный перехватчик, используя классы Reflection.Emit в C#.
Лучшее, что вы можете сделать до выпуска 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
}
Это не соответствует требованиям, определенным во втором пункте.
Сначала вам нужно изменить свой класс для реализации интерфейса (вместо реализации 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();
}
}
Вы можете добиться этого с помощью функции Перехват контейнера 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;
}
}
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) { }
}
Обратите внимание, что некоторая обработка атрибута потребуется, если нужно перехватить только какой-то метод класса. По умолчанию все публичные методы будут перехвачены.
Если вы хотите отслеживать свои методы без ограничений (без адаптации кода, без AOP Framework, без дублированного кода), позвольте мне сказать вам, вам нужна магия ...
Серьезно, я решил реализовать AOP Framework, работающую во время выполнения.
Вы можете найти здесь: NConcern .NET AOP Framework
Я решил создать эту структуру АОП, чтобы удовлетворить потребности такого рода. это простая библиотека, очень легкая. Вы можете увидеть пример регистратора на домашней странице.
Если вы не хотите использовать стороннюю сборку, вы можете просмотреть исходный код (открытый исходный код) и скопировать оба файла Aspect.Directory.cs и Aspect.Directory.Entry.cs в соответствии с вашими пожеланиями. Эти классы позволяют заменять ваши методы во время выполнения. Я просто прошу вас уважать лицензию.
Я надеюсь, что вы найдете то, что вам нужно, или убедите вас наконец использовать AOP Framework.
Может быть, уже слишком поздно для этого ответа, но вот оно.
То, чего вы хотите достичь, встроено в библиотеку MediatR.
Это мой RequestLoggerBehaviour, который перехватывает все вызовы моего бизнес-уровня.
namespace SmartWay.Application.Behaviours
{
public class RequestLoggerBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger _logger;
private readonly IAppSession _appSession;
private readonly ICreateLogGrain _createLogGrain;
public RequestLoggerBehaviour(ILogger<TRequest> logger, IAppSession appSession, IClusterClient clusterClient)
{
_logger = logger;
_appSession = appSession;
_createLogGrain = clusterClient.GetGrain<ICreateLogGrain>(Guid.NewGuid());
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var name = typeof(TRequest).Name;
_logger.LogInformation($"SmartWay request started: ClientId: {_appSession.ClientId} UserId: {_appSession.UserId} Operation: {name} Request: {request}");
var response = await next();
_logger.LogInformation($"SmartWay request ended: ClientId: {_appSession.ClientId} UserId: {_appSession.UserId} Operation: {name} Request: {request}");
return response;
}
}
}
Вы также можете создать поведение производительности, например, для отслеживания методов, выполнение которых занимает слишком много времени.
Наличие чистой архитектуры (MediatR) на вашем бизнес-уровне позволит вам сохранить ваш код в чистоте, пока вы будете применять принципы SOLID.
Вы можете увидеть, как это работает, здесь: https://thewikihow.com/video_5OtUm1BLmG0?t=1
возможный дубликат Как заменить реализацию метода во время выполнения?