Puresharp 是 .NET 4.5.2+ / .NET Core 2.1 的一组功能,旨在通过生成灵活高效的应用程序来提高生产力。
Puresharp 主要提供构建专业应用程序基础的架构工具:
该框架分为两部分:
IPuresharp 是一个专门用于重写程序集(使用Mono.Cecil )的 nuget 包,以允许它们在运行时高度可定制。 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 包,并仅在您明确需要的项目中安装Puresharp nuget 包。
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 和 crontrol 生命周期。
如何管理依赖项的生命周期?当模块设置为组合时,需要实例化模式,可以是Singleton (生命周期与容器相关的单个实例)、 Multiton (每个模块的新实例,其生命周期与模块本身相关)或Volatile (始终是一个新实例)与所有者模块相关的生命周期)。 Container 和 Module 都是 IDisposable 来释放创建的组件。
我的接口是否可以实现 IDisposable 以与生命周期管理相匹配?相反,组件的接口永远不应该实现 IDisposable 接口,这纯粹是基础设施问题。只有实现才是可能的。容器在实现 IDisposable 接口时确保正确处置实现。
为什么使用 lambda 表达式来配置组件而不是经典的泛型参数? Lambda 表达式提供了一种方法来定位要使用的构造函数、指定何时使用依赖项并捕获常量。
依赖如何配置?当您需要从容器获取依赖项时,只需在表达式中使用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;
}
}
假设我们要记录所有只读操作。为此,我们必须定义一个切入点来表示所有只读操作的方法(其中放置读取属性和操作属性)
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); }
});
}
}
我可以将多个方面编织到同一个切入点中吗?是的,只要注意编织顺序即可。
如何从切入点中删除切面? Aspect中定义了一个Release方法,用于从Pointcut中删除Aspect 。
定义切入点是否需要属性?不, Pointcut可以通过直接继承Pointcut并实现抽象方法Match来定义,该方法将MethodBase作为单个参数并返回一个布尔值以指示方法是否在Pointcut范围内。
为什么我必须使用 IPuresharp?拦截基于 IPuresharp。事实上,IPuresharp 添加了一个构建操作来重写 CIL,通过注入透明和隐藏功能来在运行时授予完全执行控制,从而使程序集“架构师友好”。
我可以拦截构造函数吗?如果是,我该如何实施?支持构造函数拦截,并将其视为另一种方法,将类型声明为第一个参数,并将返回类型声明为 void。
是否支持泛型类型和方法?注入完全支持泛型类型和方法。
我可以拦截异步方法吗?注入完全支持异步方法,并提供拦截每个异步步骤的方法。