net 反射15分钟速成
Reflection,中文翻译为反射。
这是.Net中获取运行时类型信息的方式,.Net的应用程序由几个部分:‘程序集(Assembly)’、‘模块(Module)’、‘类型(class)’组成,而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息,例如:
Assembly类可以获得正在运行的装配件信息,也可以动态的加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。
Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等等,通过Type类可以得到这些要素的信息,并且调用之。
MethodInfo包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。
诸如此类,还有FieldInfo、EventInfo等等,这些类都包含在System.Reflection命名空间下。
类型 作用 Assembly 通过此类可以加载操纵一个程序集,并获取程序集内部信息 EventInfo 该类保存给定的事件信息 FieldInfo 该类保存给定的字段信息 MethodInfo 该类保存给定的方法信息 MemberInfo 该类是一个基类,它定义了EventInfo、FieldInfo、MethodInfo、PropertyInfo的多个公用行为 Module 该类可以使你能访问多个程序集中的给定模块 ParameterInfo 该类保存给定的参数信息 PropertyInfo 该类保存给定的属性信息
这些都是废话,我们一起看几个案列就完全学会了,在此说明下,反射用到的一些基础技术有 运行运算符,type 类,这里就不过多的解释了,如有不会可以去园子里面自己去找,本人也写过一篇相关文章,简单的介绍了运行运算符。
如何得到一个类的对象
现有工程文件(项目文件)结构如下
People类代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Entity { public class People { public People() { Console.WriteLine("People被创建了"); } public People(String Name) { this.Name = Name; Console.WriteLine("People被创建了,并且people的名字是"+this.Name); } public string Name { get; set; }//公有属性,在程序实例化的过程中会自动创建私有的字段,这个字段在people 内存中开辟控件存储其值 public int Age { get; set; } public string Sex { get; set; } public string msg;//公有字段 private string qq;//私有字段 private string address;//私有属性 public string Address { get => Address; set => Address = value; } public override string ToString() { return "{" + $"name:{this.Name},age:{this.Age},sex{this.Sex}" + "}"; } public string Say() { return "hello! " + this.Name; } } }
debug 目录如下:
这里说明下,程序中,并没有引用 Entity 类库,也没有引用Entity..DLL文件,请自行引用,我们如果不实例化得到一个对象呢??正常的时候,我们都是通过new 得到一个对象,如:
using Entity; using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { People p = new People(); Console.WriteLine(p); People peop = new People("张三"); Console.WriteLine(p); Console.Read(); } } }
我们再来看下类的类型是什么?
using Entity; using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { Type t = typeof(People); Console.WriteLine(t); Type type= Type.GetType("People"); Console.WriteLine(type);//这里是得不到的,因为配件装载只能在程序集内部使用 Console.Read(); } } }
我们来学习下,如何根据类类型进行反射。
类的反射 对象无参构造函数反射
static void Main(string[] args) { Type type = typeof(People); People people= Activator.CreateInstance(type) as People;//实例化得带一个类 Console.WriteLine(people); Console.Read(); }
对象有构造函数参反射
static void Main(string[] args) { Type type = typeof(People); People people= Activator.CreateInstance(type) as People;//实例化得到一个类 Console.WriteLine(people); //实例化得到一个类,该类有一个参数 People p = Activator.CreateInstance(type, new object[] { "Wbcsky" }) as People; Console.WriteLine(p); Console.Read(); }
对象泛型反射
static void Main(string[] args) { Type type = typeof(People); People p1 = Activator.CreateInstance<People>(); Console.WriteLine(p1); Console.Read(); }
关于对象的反射,就只有这三种形式,分别是泛型反射,泛型反射有且只能得到无参数的实例对象,和普通无参反射像比较,反射反射减少了装箱拆箱的操作。有参数反射我们是按照参数的顺序,传递的object 数组。这些反射都是基于 Activator.CreateInstance 来完成的。
属性字段的反射 获取一个对象的所有属性
static void Main(string[] args) { Type type = typeof(People); System.Reflection.PropertyInfo[] p = type.GetProperties(); foreach (var item in p) { Console.WriteLine("属性名:" + item.Name + "属性类型" + item.PropertyType.FullName + "属性类型命名空间" + item.PropertyType.Namespace); } Console.Read(); }
我们都知道,在C#中,属性的封装有两种,一种全写,一种简写,全写的在某些工具书中叫做私有属性,简写的在工具书上叫做公有属性。
如:
public int Age { get; set; }
我们称为简写,工具书上叫做公有属性。
则:
private string address;//私有属性 public string Address { get => Address; set => Address = value; }
或
private string iD;
public string ID
{
get { return this.iD; }
set { this.iD = value; }
}
这种写法我们称为私有属性,私有属性中,当使用=>这种运算的,我们称为lambda表达式写法,使用this 关键字的写法,我们称为面向对象写法。不论哪一种属性,我们都叫做属性,我们在反射中获取属性使用的是Type 类的 .GetProperties()方法来获取类的全部属性。我们来看下执行结果。获取指定名称的属性和值及设置一个值
这里就不过多的介绍获取属性的值了,我们在下面介绍获取属性的值。
static void Main(string[] args) { Type type = typeof(People); System.Reflection.PropertyInfo Property = type.GetProperty("Name");//注意属性名称字符串大小写 if (Property == null) Console.Read();//如果属性名称大小写错误或者不存在,我们Property对象将会是null Console.WriteLine("属性名:" + Property.Name + "属性类型" + Property.PropertyType.FullName + "属性类型命名空间" + Property.PropertyType.Namespace); //获取属性的值 People p= Activator.CreateInstance(type) as People;//获取对象 object oName = Property.GetValue(p); //获取值 Console.WriteLine("旧" + oName); Property.SetValue(p, "abc");//设置一个值 oName = Property.GetValue(p); //获取值 Console.WriteLine("新" + oName); Console.Read(); }
看了上面的代码,我们会发现,获取属性使用的是Type类的 GetProperty方法来完成的。获取值和设置值,使用的是 PropertyInfo 类的 GetValue和Set value 来完成的。执行结果如下
因为初始化的时候是空,所以旧就什么也没有输出。有人会说了,这个没有获取到类,进行点写的方便,为什么要这么写呢,告诉你一句话,存在就是有道理的,这里可以简单的告诉,我们很多时候,一个功能更新过于频繁,我们完全可以把这个类写入配置文件中,去配置这个类对象的功能使用。理解即可,不理解清背下来代码。
获取对象的所以公有字段和私有字段在这里说明下,很多人都不明白字段和属性的区别,我这里简单说下,理解即可,不理解不影响学习,我们一个类的变量进行封装,会出现get ,set 设置这个字段的访问权限,这个封装我们称为属性,而这个变量我们叫做字段,字段不指定修饰符的时候默认为私有的。
static void Main(string[] args) { Type type = typeof(People); System.Reflection.FieldInfo[] fi = type.GetFields(); Console.WriteLine("\r\n-------------------- 获取对象的所以公有字段-------------------------------\r\n"); foreach (System.Reflection.FieldInfo item in fi) { Console.WriteLine("公有字段名" + item.Name); } Console.WriteLine("\r\n-------------------- 获取对象的所有私有字段-------------------------------\r\n"); System.Reflection.FieldInfo[] fiprivate = type.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); foreach (System.Reflection.FieldInfo item in fiprivate) { Console.WriteLine("私有字段名" + item.Name); } Console.Read(); }
这是一个难点,但是在实际开发过程中很少使用,但是这我们必须要会,否则后期写组件开发等文档,该看不懂了,准备好瓜子,咱们开始听故事了。
看了上面的代码,及字段及属性的介绍,我们会发现,输出的结果,共有的很好理解,我们类里面定义的变量 指定public 以后,我们就可以通过
GetFields ()
方法返回我们想要的公有字段数组,我们输出了名字,这里就不过多的解释了。
反射私有字段,输出的这个是什么啊,乱码七招的。
私有字段名<Name>k__BackingField
私有字段名<Age>k__BackingField
私有字段名<Sex>k__BackingField
私有字段名qq
私有字段名address
其实很好理解,我们在前面说过获取所有属性的时候说过属性分为私有和公有,其中私有属性有两种写法,其实私有属性是对私有变量的封装,也可以说是对私有字段的封装,公有属性是什么呢?获取指定的公有字段
其实公有属性在编译过程中, 为了方便JTL 公共语言运行环境更好的编译,自动生成了一个私有的字段,这个字段是根据操作系统不同生成不同前缀的私有字段,这里生成的是K_前缀的。这样我们就好理解为什么上图会多输出三个字段。
如果此处还不理解,那么请看其他博客吧本文介绍的毕竟都是基础。而实际开发过程中反射这基本使用的都是组件。
在这里就不介绍获取指定公有字段的值了,和属性获取是一样的。
static void Main(string[] args) { Type type = typeof(People); Console.WriteLine("\r\n-------------------- 获取对象的指定公有字段-------------------------------\r\n"); Console.WriteLine("字段名" + type.GetField("msg").Name); Console.Read(); }
代码很简单,只有一行。那么有人会问,那字段分为私有和共有的,为啥没有介绍获取私有属性的呢???为啥没有介绍获取指定私有字段的呢???,其实答案很简单,你看过有封装属性的时候有私有的吗,私有的是不是都说在类的内部使用,那我反射类就可以了,我外部也不使用。那私有字段呢,为啥没有,不是没有,是有但是基本不使用,因为共有属性会默认生成私有字段,这个私有字段的前缀不同,所以无法获取,没意义。所以基本没人使用。
方法和构造函数的反射 获取公有方法并调用
Type type = typeof(People); Console.WriteLine("\r\n-------------------- 获取对象的共有方法并且调用-------------------------------\r\n"); System.Reflection.MethodInfo mi = type.GetMethod("Say"); People p= Activator.CreateInstance<People>(); p.Name = "张四伙";//为了省事,这里不使用属性反射添加值了 object oReturn = mi.Invoke(p, null);//第一个参数为反射的对象,第二个参数object 数组,为参数,参数按顺序填写 Console.WriteLine(oReturn); Console.Read();
这个没有什么解释的了,前面最难的属性字段反射,我们都会了,这个就不是问题了,自己多看看代码?
获取当前类下的所有够着函数static void Main(string[] args) { Type type = typeof(People); ///获取所有的一般不会使用,这里就不过多介绍了 System.Reflection.ConstructorInfo[] info = type.GetConstructors();//获取当前类下所有够着函数 foreach (System.Reflection.ConstructorInfo item in info) { Console.WriteLine("是否为虚方法"+item.IsVirtual); Console.WriteLine("名称"+item.Name); } Console.WriteLine("\r\n-------------------- 获取当前类下参数类型匹配的够着函数-------------------------------\r\n"); System.Reflection.ConstructorInfo con = type.GetConstructor(new Type[] { typeof(string) }); object o = con.Invoke(new object[] { "zhangsan" }); People peo = o as People; Console.WriteLine(peo); Console.Read(); }
大家会说了,够着函数不就是类对象的实例化吗?,我们前面不是讲过反射类对象了吗,为什么这个里面还要获取实例化对象呢?
其实有些时候,我们在使用抽象类和接口的时候,我们通过之前学习的类的反射是一样可以做到得到类的对象,这里之说以这么讲解,因为有一些反射项目在优化的时候,会使用内部查找原则,即从够着函数开始得带类的对象,效率会更高一些。
我们在开发过程中,尽量有内而外,尽量把计算或者声明拿到程序代码执行过程中的最后去做,这样使用内存会少,效率会更高。
下边我们学习这篇文章的第二大核心。程序集反射
程序集反射
什么是程序集反射呢,加入我们三层架构,我不想引用bll层和model 层,也不想引用他们的dll,就能在业务层得带他的对象引用,这个怎么做到呢???我们一起来学习下吧!
首先程序集中删除Entity.dll 程序编译跟目录放置 ectity.dll文件。看下列代码
using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { /*装载程序集*/ System.Reflection.Assembly assembly = System.Reflection.Assembly.Load("Entity"); // System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom("Entity.bll");//使用这种方式需要写扩展名 Console.WriteLine("\r\n-------------------- 程序集反射1-------------------------------\r\n"); Type peopleType = assembly.GetType("Entity.People");//得到people 类的type 类型 object obj = Activator.CreateInstance(peopleType); System.Reflection.MethodInfo me = peopleType.GetMethod("Say"); object ret = me.Invoke(obj, null); Console.WriteLine(ret); Console.WriteLine("\r\n-------------------- 程序集反射2-------------------------------\r\n"); object PeopleObj = assembly.CreateInstance("Entity.People");//直接得到类的实例化对象 Console.WriteLine(PeopleObj); Console.Read(); } } }
代码注释已经很明确了,这里就不过多的解释了,我们来看下执行结果 。
-------------------- 程序集反射1-------------------------------
People被创建了
hello!
-------------------- 程序集反射2-------------------------------
People被创建了
{name:,age:0,sex}
在程序集反射中,我们就没有办法在.属性 .字段 .方法的调用了,这个时候,我们只能通过属性,方法的反射区调用了,这里演示的不多,就两种常用的案列,本文只是介绍了开发过程中常用的案列。
总结及扩展
1.反射一般是用在序列化无法完成的情况下,比如接口返回想xml,而这个xml 经常变动,并没有一个指定的规律,这个时候我们就不能用linq to xml 等反序列化对象了。这个时候就应当使用反射了。
2.真正开发过程中,反射不是是向上面这么写的,真正的反射是使用组件来完成的,一般也不会使用程序集反射,除非这个框架的某个功能模块更新频繁,我们可以使用不同的反射区完成,只需要在xml 文件中配置下就可以了。
3.在这里简单介绍下组件反射,不是说开发过程中不会有程序集等反射,而是大多数的情况下组件反射就已经能满足我们的需求了,如AutoFac组件,等其他的。
4.反射技术点一般对应的技术点有 IOC 翻转,依赖倒置,依赖注入等
下边分享一篇文章,之所以写本文,就是因为下边这篇文文章介绍的太主流,很多人不会使用,Autofac是net core 2.0里面的组件,请看下边的文章
//格式
//var builder = new ContainerBuilder();
Autofac是一个轻量级的依赖注入的框架,同类型的框架还有Spring.NET,Unity,Castle等。
Autofac的使用有一个非常让人郁闷的地方,就是服务器要求安装有Microsoft .NET Framework 4 KB2468871。该补丁的地址是:http://www.microsoft.com/zh-cn/download/confirmation.aspx?id=3556 如果不安装,则运行程序会报如下错误:
具体信息可以到这里去查看:https://code.google.com/p/autofac/wiki/FrequentlyAskedQuestions
这篇文章以一个最简单的例子,开始Autofac的学习:
namespace ConsoleApplication3{ class Program { static void Main(string[] args) { ContainerBuilder builder = newContainerBuilder(); builder.RegisterType<AutoFacManager>(); builder.RegisterType<Worker>().As<IPerson>(); using (IContainer container = builder.Build()) { AutoFacManager manager = container.Resolve<AutoFacManager>(); manager.Say(); } Console.ReadKey(); } } public interface IPerson { void Say(); } public class Worker : IPerson { public void Say() { Console.WriteLine("我是一个工人!"); } } public class Student : IPerson { public void Say() { Console.WriteLine("我是一个学生!"); } } public class AutoFacManager { IPerson person; public AutoFacManager(IPerson MyPerson) { person = MyPerson; } public void Say() { person.Say(); } }}以上例子,是一个最简单的例子,输出如下:
简单解释:
1、ContainerBuilder类型的作用
组件通过ContainerBuilder的对象注册。
2、组件
对象需要从组件中来获取,比如例子中的Worker类的实例就需要从组件中获取。
3、哪些实例可以作为组件
Lambda表达式 一个类型 一个预编译的实例 实例类型所在的程序集4、容器
ContainerBuilder的Build()方法可以创建容易,从容器的Resolve()方法能够获得对象。
5、为了指定组件服务是某一接口
As()方法将用于注册时之指定:builder.RegisterTye<TaskController>().As<TController>();
6、组件的依赖关系
组件的依赖关系主要通过接口实现,如Worker:IPerson,
一、组件创建出来的对象需要从组件中来获取,组件的创建有如下4种(延续第一篇的Demo,仅仅变动所贴出的代码)方式:
1、类型创建RegisterType
AutoFac能够通过反射检查一个类型,选择一个合适的构造函数,创造这个对象的实例。主要通过RegisterType<T>() 和 RegisterType(Type) 两个方法以这种方式建立。
ContainerBuilder使用 As() 方法将Component封装成了服务使用。
builder.RegisterType<AutoFacManager>(); builder.RegisterType<Worker>().As<IPerson>();2、实例创建
builder.RegisterInstance<AutoFacManager>(new AutoFacManager(new Worker()));单例
提供示例的方式,还有一个功能,就是不影响系统中原有的单例:
builder.RegisterInstance(MySingleton.GetInstance()).ExternallyOwned(); //将自己系统中原有的单例注册为容器托管的单例这种方法会确保系统中的单例实例最终转化为由容器托管的单例实例。
3、Lambda表达式创建
Lambda的方式也是Autofac通过反射的方式实现
builder.Register(c => new AutoFacManager(c.Resolve<IPerson>())); builder.RegisterType<Worker>().As<IPerson>();4、程序集创建
程序集的创建主要通过RegisterAssemblyTypes()方法实现,Autofac会自动在程序集中查找匹配的类型用于创建实例。
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()); //在当前正在运行的程序集中找builder.RegisterType<Worker>().As<IPerson>();5、泛型注册
泛型注册通过RegisterGeneric() 这个方法实现,在容易中可以创建出泛型的具体对象。
//泛型注册,可以通过容器返回List<T> 如:List<string>,List<int>等等builder.RegisterGeneric(typeof(List<>)).As(typeof(IList<>)).InstancePerLifetimeScope(); using (IContainer container = builder.Build()) { IList<string> ListString = container.Resolve<IList<string>>(); }6、默认的注册
如果一个类型被多次注册,以最后注册的为准。通过使用PreserveExistingDefaults() 修饰符,可以指定某个注册为非默认值。
ContainerBuilder builder = new ContainerBuilder(); builder.RegisterType<AutoFacManager>(); builder.RegisterType<Worker>().As<IPerson>(); builder.RegisterType<Student>().As<IPerson>().PreserveExistingDefaults();//指定Student为非默认值 using (IContainer container = builder.Build()) { AutoFacManager manager = container.Resolve<AutoFacManager>(); manager.Say(); //输出我是一个工人 }如果不使用PreserveExistingDefaults(),那么将输出“我是一个学生”。
二、服务Autofac有三种典型的方式区分服务,同一个服务的不同实现可以由类型,名称和键区分。
1、类型
类型是描述服务的基本方法
builder.RegisterType<Worker>().As<IPerson>(); //IPerson类型的服务和Worker的组件连接起来,这个服务可以创建Worker类的实例并且上面的服务在自动装备中也有效
AutoFacManager manager = container.Resolve<AutoFacManager>();2、名字
服务可以进一步按名字识别。使用这种方式时,用 Named()注册方法代替As()以指定名字:
builder.RegisterType<Worker>().Named<IPerson>("worker");使用Name可以检索服务创建实例:
IPerson p = container.ResolveNamed<IPerson>("worker");ResolveNamed()只是Resolve()的简单重载,指定名字的服务其实是指定键的服务的简单版本。
3、键
有Name的方式很方便,但是值支持字符串,但有时候我们可能需要通过其他类型作键。
例如,使用枚举作为key:
public enum DeviceState { Worker, Student }使用key注册服务,通过Keyed<T>()方法:
builder.RegisterType<Student>().Keyed<IPerson>(DeviceState.Student);显式检索
使用key检索服务以创建实例,通过ResolveKeyd()方法:
IPerson p = container.ResolveKeyed<IPerson>(DeviceState.Student);ResolveKeyd()会导致容器被当做 Service Locator使用,这是不被推荐的。应该使用IIndex type替代。
IIndex索引
Autofac.Features.Indexed.IIndex<K,V>是Autofac自动实现的一个关联类型。component可以使用IIndex<K,V>作为参数的构造函数从基于键的服务中选择需要的实现。
builder.RegisterType<Student>().Keyed<IPerson>(DeviceState.Student); using (IContainer container = builder.Build()) {IIndex<DeviceState, IPerson> IIndex = container.Resolve<IIndex<DeviceState, IPerson>>(); IPerson p = IIndex[DeviceState.Student]; p.Say(); //输出我是一个学生 }IIndex中第一个泛型参数要跟注册时一致,在例子中是DeviceState枚举。其他两种注册方法没有这样的索引查找功能,这也是为什么设计者推荐Keyed注册的原因之一。
三、自动装配从容器中的可用服务中选择一个构造函数来创造对象,这个过程叫做自动装配。这个过程是通过反射实现的,所以实际上容器创造对象的行为比较适合用在配置环境中。
1、选择构造函数
Autofac默认从容器中选择参数最多的构造函数。如果想要选择一个不同的构造函数,就需要在注册的时候就指定它。
builder.RegisterType(typeof(Worker)).UsingConstructor(typeof(int));这种写法将指定调用Worker(int)构造函数,如该构造函数不存在则报错。
2、额外的构造函数参数
有两种方式可以添加额外的构造函数参数,在注册的时候和在检索的时候。在使用自动装配实例的时候这两种都会用到。
注册时添加参数
使用WithParameters()方法在每一次创建对象的时候将组件和参数关联起来。
List<NamedParameter> ListNamedParameter = new List<NamedParameter>() { new NamedParameter("Id", 1), newNamedParameter("Name", "张三") }; builder.RegisterType<Worker>().WithParameters(ListNamedParameter).As<IPerson>(); 在检索阶段添加参数
在Resolve()的时候提供的参数会覆盖所有名字相同的参数,在注册阶段提供的参数会覆盖容器中所有可能的服务。
3、自动装配
至今为止,自动装配最大的作用就是减少重复配置。许多相似的component无论在哪里注册,都可以通过扫描使用自动装配。
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).As<IPerson>();在需要的时候,依然可以创建指定的构造函数创建指定的类。
builder.Register(c => new Worker(2,"关羽")); 四、程序集扫描1、扫描
Autofac可以使用约定在程序集中注册或者寻找组件。
Autofac可以根据用户指定的规则在程序集中注册一系列的类型,这种方法叫做convention-driven registration或者扫描。
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.EndsWith("Manager"));每个RegisterAssemblyTypes方法只能应用一套规则。如果有多套不同的集合要注册,那就有必要多次调用RegisterAssemblyTypes。
2、选择类型
RegisterAssemblyTypes接受程序集的集合。默认情况下,程序集中所有公共具体的类都会被注册。
如果想要过滤注册的类型,可以使用Where.向下面这样:
Where(t => t.Name.EndsWith("Manager"))如果想要排除某些类型,使用Except():
Except<AutoFacManager)>()或者,自定义那些已经排除的类型的注册:
Except<Worker>(ct =>ct.As<IPerson>().SingleInstance())多个过滤器可以同时使用,这时他们之间是AND的关系。
3、指定服务
RegisterAssemblyTypes这个注册方法是注册单个方法的超集,所以类似As的方法也可以用在程序集中,例如
As<IPerson>();As和Named这两个方法额外的重载方法接受lambda表达式来决定服务会提供什么样的类型。
五、事件1、激活事件
在component生命周期的不同阶段使用事件。
Autofac暴露五个事件接口供实例的按如下顺序调用
OnRegistered OnPreparing OnActivated OnActivating OnRelease这些事件会在注册的时候被订阅,或者被附加到IComponentRegistration 的时候。
builder.RegisterType<Worker>().As<IPerson>() .OnRegistered(e => Console.WriteLine("在注册的时候调用!")) .OnPreparing(e => Console.WriteLine("在准备创建的时候调用!")) .OnActivating(e => Console.WriteLine("在创建之前调用!")) .OnActivated(e => Console.WriteLine("创建之后调用!")) .OnRelease(e => Console.WriteLine("在释放占用的资源之前调用!"));以上示例输出如下:
OnActivating
组件被创建之前调用,在这里你可以:
将实例转向另外一个或者使用代理封装它 进行属性注入 执行其他初始化工作OnActivated
在component被完全创建的时候调用一次。在这个时候你可以执行程序级别的一些工作(这些工作依赖于对象被完全创建)-这种情况很罕见。
OnRelease
替代component的标准清理方法。实现了IDisposable 接口的标准清理方法(没有标记为ExternallyOwned) 通过调用Dispose 方法。没有实现IDisposable或者被标记为ExternallyOwned的清理方法是一个空函数-不执行任何操作。OnRelease 就是用来覆盖默认的清理行为的。
六、属性注入属性注入使用可写属性而不是构造函数参数实现注入。
示例:
builder.Register(c => new AutoFacManager { person = c.Resolve<IPerson>() }); builder.RegisterType<Worker>().As<IPerson>();为了提供循环依赖(就是当A使用B的时候B已经初始化),需要使用OnActivated事件接口:
builder.Register(c => new AutoFacManager()).OnActivated(e => e.Instance.person = e.Context.Resolve<IPerson>()); builder.RegisterType<Worker>().As<IPerson>();通过反射,使用PropertiesAutowired()修饰符注入属性:
builder.RegisterType<AutoFacManager>().PropertiesAutowired(); builder.RegisterType<Worker>().As<IPerson>();如果你预先知道属性的名字和值,你可以使用:
builder.RegisterType<AutoFacManager>().WithProperty("person", new Worker()); builder.RegisterType<Worker>().As<IPerson>(); 七、方法注入可以实现方法注入的方式有两种。
1、使用Activator
如果你使用委托来激活,只要调用这个方法在激活中
builder.Register(c => { var result = new AutoFacManager(); result.SetDependency(c.Resolve<IPerson>()); return result; });注意,使用这种方法,AutoFacManager类里必须要有这个方法:
public void SetDependency(IPerson MyPerson) { person = MyPerson; }2、使用Activating Handler
如果你使用另外一种激活,比如反射激活,创建激活的事件接口OnActivating,这种方式仅需一行代码:
builder.Register<AutoFacManager>(c => new AutoFacManager()).OnActivating(e => e.Instance.SetDependency(newWorker())); 八、Resolve的参数当注册或者检索component的时候可以使用参数。
1、传递参数给Resolve
Resolve接受可变参数或IEnumerable<T>传入多个值
using (IContainer container = builder.Build()) { AutoFacManager manager = container.Resolve<AutoFacManager>(newNamedParameter("name", "刘备")); Console.WriteLine(manager.Name); //输出 刘备 manager.Say(); }此时,AutoFacManager下必须添加如下构造函数
public AutoFacManager(string name,IPerson MyPerson) { Name = name; person = MyPerson; }2、可用的参数类型
Autofac提供几种不同的参数对应策略:
NamedParameter :像上面那样对应的参数名字 TypedParameter:对应到参数的类型(必须是具体的类型) ResolvedParameter:灵活的参数匹配 NamedParameter 和TypedParameter:只能提供常量参数3、从表达式中使用参数
如果使用表达式注册的方式,可以使用第二个可用的委托参数来获得参数。
builder.Register((c, p) => new AutoFacManager(p.Named<string>("name"), c.Resolve<IPerson>())); builder.RegisterType<Worker>().As<IPerson>(); using (IContainer container = builder.Build()) { AutoFacManager manager = container.Resolve<AutoFacManager>(new NamedParameter("name", "刘备")); Console.WriteLine(manager.Name); //输出刘备 manager.Say(); } 九、元数据Autofac提供一些机制去创建和使用component的元数据。元数据是存储component中的关于这个component的信息,不需要创建实例也能访问。
1、在注册的时候添加元数据
值描述的元数据在注册阶段和component联系起来,每个元数据都是一个键值对:
builder.RegisterType<AutoFacManager>(); builder.Register(c => new Worker()).As<IPerson>().WithMetadata("大将", "赵云");用XML文件可以表示为:
<component type="ConsoleApplication3.Program.Worker, ConsoleApplication3" service="ConsoleApplication3.Program.IPerson, ConsoleApplication3" > <metadata> <item name="大将" value="赵云" type="System.String" /> </metadata> </component>2、使用元数据
不用于一般的属性,元数据和component本身是相互独立额度。
这使得在运行条件下从很多component中选择一个时非常有用,或者元数据不是component实例的固有属性时。元数据可以表述ITask 执行的时间,或者实现了ICommand的按钮标题。
另外一些component可以通过Meta 使用元数据。
十、循环依赖循环依赖是指运行期间对象之间的相互依赖
目前,Autofac仅仅由处理构造函数/属性依赖的方法。
1、构造函数/属性依赖
使用含有属性依赖的类时,使用Activated事件的InjectUnsetProperties。
public class Student { public Student(Worker worker) { } } public class Worker { public Student Student { get; set; } } ContainerBuilder cb = new ContainerBuilder(); cb.Register<Student>(); cb.Register<Worker>().OnActivated(ActivatedHandler.InjectUnsetProperties); 十一、泛型给定一个开放的泛型,Autofac会提供一个具体的实现。
开放的泛型类型使用泛型服务注册需要给定一个服务类型和一个实现类型。
builder.RegisterGeneric(typeof(List<>)).As(typeof(IList<>)); using (IContainer container = builder.Build()) { var tt = container.Resolve<IList<int>>(); Console.WriteLine(tt.GetType().FullName); }Autofac关心泛型约束。如果一个有约束的实现类型对服务不可用,那么这个实现类型将被忽略。
十二、适配器和装饰器Autofac提供一些机制来实现适配器模式和装饰器模式。
1、适配器
一个适配器使用一个服务并且适配另外一个。
如果一个适配器在Autofac中被注册,Autofac会为每一个适配服务的实现创建单独的适配器。
这个介绍性的文章描述了适配器在Autofac中是如何实现的。
2、装饰器
装饰器像适配器一样,在其中封装了一个具体的服务的实现,但是和适配器相比,装饰器暴露出的服务和它封装的一样。
十三、实例生命周期实例生命周期决定在同一个服务的每个请求的实例是如何共享的。
当请求一个服务的时候,Autofac会返回一个单例 (single instance作用域), 一个新的对象 (per lifetime作用域) 或者在某种上下文环境中的单例。比如 一个线程 或者一个HTTP请求 (per lifetime 作用域)。
这条规则适用于显式调用Resolve从容器中检索对象或者满足依赖而隐式实现的对象。
1、Per Dependency
在其他容器中也称作瞬态或者工厂,使用Per Dependency作用域,服务对于每次请求都会返回互补影响实例。
在没有指定其他参数的情况下,这是默认是作用域。
builder.RegisterType<Worker>(); // or builder.RegisterType<Worker>().InstancePerDependency();2、Single Instance
使用Single Instance作用域,所有对父容器或者嵌套容器的请求都会返回同一个实例。
builder.RegisterType<Worker>().SingleInstance();3、Per Lifetime Scope
这个作用域适用于嵌套的生命周期。一个使用Per Lifetime 作用域的component在一个 nested lifetime scope内最多有一个实例。
当对象特定于一个工作单元时,这个非常有用。比如,一个HTTP请求,每一个工作单元都会创建一个nested lifetime,如果在每一次HTTP请求中创建一个nested lifetime,那么其他使用 per-lifetime 的component在每次HTTP请求中只会拥有一个实例。
这种配置模型在其他容器中等价于per-HTTP-request, per-thread等。
builder.RegisterType<Worker>().InstancePerLifetimeScope();ASP.NET和WCF集成中,每一次web请求或者方法调用,InstancePerLifetimeScope会被默认附加到component上。
4、上下文
上下文作用域和per-lifetime作用域类似,但是对可见性提供更多显示的控制。
在大多数程序中,同一层次的容器嵌套代表一个工作单元,如果需要多层嵌套(例如global->request->transation),可以使用标签确保component在多层结构中的某一层共享。
builder.RegisterType<XWorker>().InstancePerMatchingLifetimeScope(MyContextHierarchy.UserSession);提供的标签和生命周期作用域是对应的
var userSessionLifetime = container.BeginLifetimeScope(); userSessionLifetime.Tag = MyContextHierarchy.UserSession;
1、通过配置的方式使用Autofac
<?xml version="1.0"?> <configuration> <configSections> <section name="autofac" type="Autofac.Configuration.SectionHandler, Autofac.Configuration"/> </configSections> <autofac defaultAssembly="ConsoleApplication3"> <components> <component type="ConsoleApplication3.Worker, ConsoleApplication3" service="ConsoleApplication3.IPerson" /> </components> </autofac> </configuration>2、通过RegisterModule方式使用配置文件中的信息
static void Main(string[] args) { ContainerBuilder builder = new ContainerBuilder(); builder.RegisterType<AutoFacManager>(); builder.RegisterModule(new ConfigurationSettingsReader("autofac")); using (IContainer container = builder.Build()) { AutoFacManager manager = container.Resolve<AutoFacManager>(); manager.Say(); } Console.ReadKey(); }3、通过Register的方式
builder.RegisterModule(new ConfigurationSettingsReader("autofac")); builder.Register(c => newAutoFacManager(c.Resolve<IPerson>()));原文 http://www.cnblogs.com/kissdodog/p/3623983.html
上一篇: 职场生活爆笑一场
下一篇: 详解JUC并发编程之锁