Puresharp — это набор функций для .NET 4.5.2+/.NET Core 2.1, предназначенных для повышения производительности за счет создания гибких и эффективных приложений.
Puresharp в основном предоставляет архитектурные инструменты для создания основ профессиональных приложений:
Эта структура разделена на 2 части:
IPuresharp — это пакет nuget, предназначенный для перезаписи сборок (с использованием Mono.Cecil ), чтобы обеспечить их широкие возможности настройки во время выполнения. IPuresharp не добавляет ссылку на новую библиотеку в сборки, а включает только процесс последующей сборки для автоматического перезаписи сборок сразу после успешной сборки.
Install-Package IPuresharp -Version 5.0.5
Его можно использовать вручную с помощью командной строки для управления сборками сторонних производителей.
IPuresharp.exe "FullnameToAssembly.dll"
Puresharp — это пакет nuget, предлагающий различные функции, полезные для разработки здоровой и продуктивной архитектуры. Этот пакет также включает в себя все возможности для легкой обработки элементов, входящих в состав средства записи IL IPuresharp. Пакет nuget добавляет библиотеку (Puresharp.dll) без каких-либо других зависимостей.
Install-Package Puresharp -Version 5.0.5
Примечание. Рекомендуется устанавливать пакет IPuresharp nuget во всех проектах и устанавливать пакет nuget Puresharp только в тех проектах, где он вам явно необходим.
Глобальный рабочий процесс DI-контейнера аналогичен другим: настройка композиции, создание контейнера и создание экземпляров из контейнера некоторых компонентов.
Пример интерфейсов для настройки
public interface IA
{
}
public interface IB
{
}
public interface IC
{
}
Пример реализации привязки к интерфейсам
public class A : IA
{
public A(IB b, IC c)
{
}
}
public class B : IB
{
public B(IC c, int constant)
{
}
}
public class C : IC
{
}
Создать композицию
var _composition = new Composition();
Состав установки для IA, IB, IC соответственно A, B, C
_composition.Setup<IA>(() => new A(Metadata<IB>.Value, Metadata<IC>.Value), Instantiation.Multiton);
_composition.Setup<IB>(() => new B(Metadata<IC>.Value, 28), Instantiation.Multiton);
_composition.Setup<IC>(() => new C(), Instantiation.Multiton);
Создать контейнер из настройки композиции
var _container = _composition.Materialize();
Создать экземпляр модуля IA из контейнера
using (var _module = _container.Module<IA>())
{
var _ia = _module.Value;
}
примечание: модуль является IDisposable и контролирует жизненный цикл для всех зависимостей.
Как управляется жизненный цикл зависимостей? Когда модуль устанавливается в композицию, требуется режим создания экземпляра, который может быть Singleton (один экземпляр с жизненным циклом, связанным с контейнером), Multiton (новый экземпляр для каждого модуля с жизненным циклом, связанным с самим модулем) или Volatile (всегда новый экземпляр). с жизненным циклом, связанным с модулем владельца). Контейнер и модуль являются IDisposable для выпуска созданных компонентов.
Могут ли мои интерфейсы реализовать IDisposable, соответствующий управлению жизненным циклом? Напротив, интерфейс компонента никогда не должен реализовывать интерфейс IDisposable, что является чисто инфраструктурной проблемой. Возможны только реализации. Контейнер обеспечивает правильное удаление реализаций при реализации интерфейса IDisposable.
Зачем использовать лямбда-выражение для настройки компонентов вместо классического универсального параметра? Лямбда-выражение позволяет использовать целевой конструктор, указывать, когда использовать зависимости, и фиксировать константу.
Как настраивается зависимость? Просто используйте Metadata<T>.Value в выражении, когда вам нужно получить обратную зависимость от контейнера.
Предотвращает ли внедрение конструктора циклическую ссылку между компонентами? Нет, циклические ссылки — это особенность. Когда создается экземпляр, на самом деле это не так: создается ленивый прокси-экземпляр, чтобы минимизировать сохранение неиспользуемых ресурсов и разрешить циклические ссылки.
В предварительной версии для настройки компонента используются только конструкторы. Ограничено ли внедрение конструктора? Нет, выражения абсолютно открыты. Вы можете внедрять статические методы, конструкторы, члены и даже смешивать разные стили.
Рабочий процесс:
Пример интерфейса
[AttributeUsage(AttributeTargets.Method)]
public class Read : Attribute
{
}
[AttributeUsage(AttributeTargets.Method)]
public class Operation : Attribute
{
}
public interface IService
{
[Operation]
void SaveComment(int id, string text);
[Read]
[Operation]
string GetComment(int id);
}
Пример реализации
public class Service : IService
{
public void SaveComment(int id, string text)
{
}
public string GetComment(int id)
{
return null;
}
}
Предположим, мы хотим регистрировать все операции только для чтения. Для этого нам нужно определить Pointcut , который представляет все методы, которые работают только для чтения (где размещены атрибуты Read и атрибут Operation).
public class ReadonlyOperation : Pointcut.And<Pointcut<Operation>, Pointcut<Read>>
{
}
Определите совет для регистрации перед Trace.WriteLine, например, при вызове методов.
public class Log : IAdvice
{
private MethodBase m_Method;
public Log(MethodBase method)
{
this.m_Method = method;
}
public void Instance<T>(T instance)
{
}
public void Argument<T>(ref T value)
{
}
public void Begin()
{
Trace.WriteLine(this.m_Method);
}
public void Await(MethodInfo method, Task task)
{
}
public void Await<T>(MethodInfo method, Task<T> task)
{
}
public void Continue()
{
}
public void Throw(ref Exception exception)
{
}
public void Throw<T>(ref Exception exception, ref T value)
{
}
public void Return()
{
}
public void Return<T>(ref T value)
{
}
public void Dispose()
{
}
}
Определите аспект , который использует журнал рекомендаций
public class Logging : Aspect
{
override public IEnumerable<Advisor> Manage(MethodBase method)
{
yield return Advice
.For(method)
.Around(() => new Log(method));
}
}
Создайте экземпляр Aspect и привяжите его к нашей операции ReadonlyOperation Pointcut.
var _logging = new Logging();
_logging.Weave<ReadonlyOperation>();
Поздравляем, аспект регистрации теперь внедрен во все контракты операций только для чтения.
Вот набор образцов, позволяющий увидеть различные способы создания и советника.
public class Logging : Aspect
{
override public IEnumerable<Advisor> Manage(MethodBase method)
{
//Use classic interceptor to create an 'Around' advisor (place break points in interceptor methods to test interception).
yield return Advice
.For(method)
.Around(() => new Interceptor());
//Use linq expression to generate a 'Before' advisor.
yield return Advice
.For(method)
.Before(invocation =>
{
return Expression.Call
(
Metadata.Method(() => Console.WriteLine(Metadata<string>.Value)),
Expression.Constant($"Expression : { method.Name }")
);
});
//Use linq expression to generate a 'Before' advisor.
yield return Advice
.For(method)
.Before
(
Expression.Call
(
Metadata.Method(() => Console.WriteLine(Metadata<string>.Value)),
Expression.Constant($"Expression2 : { method.Name }")
)
);
//Use ILGeneration from reflection emit API to generate a 'Before' advisor.
yield return Advice
.For(method)
.Before(advice =>
{
advice.Emit(OpCodes.Ldstr, $"ILGenerator : { method.Name }");
advice.Emit(OpCodes.Call, Metadata.Method(() => Console.WriteLine(Metadata<string>.Value)));
});
//Use simple Action to generate a 'Before' advisor.
yield return Advice
.For(method)
.Before(() => Console.WriteLine($"Action : { method.Name }"));
//Use an expression to generate an 'After-Returning-Value' Advisor
yield return Advice
.For(method)
.After()
.Returning()
.Value(_Execution =>
{
return Expression.Call
(
Metadata.Method(() => Console.WriteLine(Metadata<string>.Value)),
Expression.Call
(
Metadata.Method(() => string.Concat(Metadata<string>.Value, Metadata<string>.Value)),
Expression.Constant("Returned Value : "),
Expression.Call(_Execution.Return, Metadata<object>.Method(_Object => _Object.ToString()))
)
);
});
//Validate an email parameter value.
yield return Advice
.For(method)
.Parameter<EmailAddressAttribute>()
.Validate((_Parameter, _Attribute, _Value) =>
{
if (_Value == null) { throw new ArgumentNullException(_Parameter.Name); }
try { new MailAddress(_Value.ToString()); }
catch (Exception exception) { throw new ArgumentException(_Parameter.Name, exception); }
});
}
}
Могу ли я объединить несколько аспектов в один и тот же Pointcut? Да, только будьте осторожны с порядком плетения.
Как удалить аспект из Pointcut? В Aspect определен метод Release , позволяющий избавиться от Aspect из Pointcut .
Требуются ли атрибуты для определения Pointcut? Нет, Pointcut может быть определен путем прямого наследования от Pointcut и реализации абстрактного метода Match , который принимает MethodBase в качестве единственного аргумента и возвращает логическое значение, указывающее, находится ли метод в области действия Pointcut .
Почему мне нужно использовать IPuresharp? Перехват основан на IPuresharp. Действительно, IPuresharp добавляет действие сборки, чтобы переписать CIL, чтобы сделать сборку «дружественной к архитектуре», путем внедрения прозрачных и скрытых функций, чтобы обеспечить полный контроль выполнения во время выполнения.
Могу ли я перехватить конструктор? Если да, то как мне это реализовать? Перехват конструктора поддерживается и рассматривается как другой метод с объявлением типа в качестве первого аргумента и void для типа возвращаемого значения.
Поддерживаются ли универсальные типы и методы? Универсальные типы и методы полностью поддерживаются путем внедрения.
Могу ли я перехватить асинхронные методы? Асинхронные методы полностью поддерживаются внедрением и предлагают способ перехвата каждого асинхронного шага.