02-Unity深入浅出(二)
一. Unity声明周期
Unity容器为我们提供了6种生命周期,便于我们根据项目需求来选择使用。
(1). 瞬时。默认省略即为瞬时,无论单线程还是多线程,每次都重新创建对象。new TransientLifetimeManager()
(2). 容器单例。只要是同一个Unity容器创建的同一个类型的对象,无论是线程之间还是单线程内都是单例的。new ContainerControlledLifetimeManager()
(3). 线程单例。同一个线程内创建的同一个类型的对象,都是单例的。但线程之间不是单例的。new PerThreadLifetimeManager()
(4). 分级容器单例。unity容器可以创建很多子容器,每个子容器无论怎么创建对象,都是单例的,但是子容器之间不是单例的。new HierarchicalLifetimeManager()
(5). 外部可释放单例。在不销毁的情况下,每次Resolve都会返回同一个对象,即是单例的;销毁后,重新创建一个新的对象,销毁后创建的新对象又是单例的。new ExternallyControlledLifetimeManager()
(6).循环引用。new PerResolveLifetimeManager(),不推荐使用,暂不测试。
下面同样先通过代码的形式来测试以上几种情况。
(1). 瞬时
1 { 2 Console.WriteLine("------------------- 05-Unity的生命周期-瞬时(1) -------------------"); 3 IUnityContainer container = new UnityContainer(); 4 container.RegisterType<IPhone, AndroidPhone>(new TransientLifetimeManager()); //默认省略就是瞬时的 5 6 //下面测试多线程中创建的对象是否是单例的(iphone1是一个线程 iphone2和iphone3是同一个线程) 7 IPhone iphone1 = null; 8 Action act1 = () => 9 { 10 iphone1 = container.Resolve<IPhone>(); 11 Console.WriteLine("iphone1由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 12 }; 13 var result1 = act1.BeginInvoke(null, null); 14 IPhone iphone2 = null; 15 Action act2 = () => 16 { 17 iphone2 = container.Resolve<IPhone>(); 18 Console.WriteLine("iphone2由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 19 }; 20 //在act2的异步回调中创建iphone3(iphone2和iphone3是一个线程创建的) 21 IPhone iphone3 = null; 22 var result2 = act2.BeginInvoke(t => 23 { 24 iphone3 = container.Resolve<IPhone>(); 25 Console.WriteLine("iphone3由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 26 //代表两个不同线程创建的对象 27 Console.WriteLine("iphone1和iphone2是否相等?{0}", object.ReferenceEquals(iphone1, iphone2)); 28 //代表同一个线程创建的两个对象 29 Console.WriteLine("iphone2和iphone3是否相等?{0}", object.ReferenceEquals(iphone2, iphone3)); 30 }, null); 31 32 //线程等待 33 act1.EndInvoke(result1); 34 act2.EndInvoke(result2); 35 36 //总结:瞬时创建无论单个线程内还是多个线程之间,都不是单例的,每次调用都要重新创建对象 37 38 }
分析:iphone2和iphone3是同一个线程创建的,iphone1是单独一个线程创建的。经过结果分析:iphone1和iphone2不相等,iphone2和iphone3不相等,证明:瞬时容器无论是线程内,还是线程与线程之间每次都是重新创建的,都不是单例。
(2). 容器单例
1 { 2 Console.WriteLine("------------------- 05-Unity的生命周期-容器单例(2) -------------------"); 3 IUnityContainer container = new UnityContainer(); 4 container.RegisterType<IPhone, AndroidPhone>(new ContainerControlledLifetimeManager()); 5 6 //下面测试多线程中创建的对象是否是单例的(iphone1是一个线程 iphone2和iphone3是同一个线程) 7 IPhone iphone1 = null; 8 Action act1 = () => 9 { 10 iphone1 = container.Resolve<IPhone>(); 11 Console.WriteLine("iphone1由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 12 }; 13 var result1 = act1.BeginInvoke(null, null); 14 IPhone iphone2 = null; 15 Action act2 = () => 16 { 17 iphone2 = container.Resolve<IPhone>(); 18 Console.WriteLine("iphone2由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 19 }; 20 //在act2的异步回调中创建iphone3(iphone2和iphone3是一个线程创建的) 21 IPhone iphone3 = null; 22 var result2 = act2.BeginInvoke(t => 23 { 24 iphone3 = container.Resolve<IPhone>(); 25 Console.WriteLine("iphone3由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 26 //代表两个不同线程创建的对象 27 Console.WriteLine("iphone1和iphone2是否相等?{0}", object.ReferenceEquals(iphone1, iphone2)); 28 //代表同一个线程创建的两个对象 29 Console.WriteLine("iphone2和iphone3是否相等?{0}", object.ReferenceEquals(iphone2, iphone3)); 30 }, null); 31 32 //线程等待 33 act1.EndInvoke(result1); 34 act2.EndInvoke(result2); 35 36 //总结:容器单例:只要是同一个Unity容器创建的一个类,无论是线程之间还是单线程内都是单例的 37 }
分析:iphone2和iphone3是同一个线程创建的,iphone1是单独一个线程创建的。经过结果分析:iphone1和iphone2相等,iphone2和iphone3相等,证明:容器单例无论是线程内,还是线程与线程之间都是单例的。
(3). 线程单例
1 { 2 Console.WriteLine("------------------- 05-Unity的生命周期-线程单例(3) -------------------"); 3 IUnityContainer container = new UnityContainer(); 4 container.RegisterType<IPhone, AndroidPhone>(new PerThreadLifetimeManager()); 5 6 //下面测试多线程中创建的对象是否是单例的(iphone1是一个线程 iphone2和iphone3是同一个线程) 7 IPhone iphone1 = null; 8 Action act1 = () => 9 { 10 iphone1 = container.Resolve<IPhone>(); 11 Console.WriteLine("iphone1由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 12 }; 13 var result1 = act1.BeginInvoke(null, null); 14 IPhone iphone2 = null; 15 Action act2 = () => 16 { 17 iphone2 = container.Resolve<IPhone>(); 18 Console.WriteLine("iphone2由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 19 }; 20 //在act2的异步回调中创建iphone3(iphone2和iphone3是一个线程创建的) 21 IPhone iphone3 = null; 22 var result2 = act2.BeginInvoke(t => 23 { 24 iphone3 = container.Resolve<IPhone>(); 25 Console.WriteLine("iphone3由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 26 //代表两个不同线程创建的对象 27 Console.WriteLine("iphone1和iphone2是否相等?{0}", object.ReferenceEquals(iphone1, iphone2)); 28 //代表同一个线程创建的两个对象 29 Console.WriteLine("iphone2和iphone3是否相等?{0}", object.ReferenceEquals(iphone2, iphone3)); 30 }, null); 31 32 //线程等待 33 act1.EndInvoke(result1); 34 act2.EndInvoke(result2); 35 36 /* 37 * 总结:线程单例:同一个线程内,eg:iphone2和iphone3,都是AndroidPhone类型,他是单例的,不重复创建,但是线程与线程之间创建的对象就不是单例的了。 38 * 与框架中EF上下文 利用CallContext保存的原理一致 39 一般来说不建议在使用RegisterInstance对已存在的对象注册关系时使用PerThreadLifetimeManager,因为此时的对象已经在一个线程内创建了,如果再使用这个生命周期管理器,将无法保证其正确调用 40 */ 41 }
分析:iphone2和iphone3是同一个线程创建的,iphone1是单独一个线程创建的。经过结果分析:iphone1和iphone2不相等,iphone2和iphone3相等,证明:线程单例在线程内是单例的,但线程与线程之间不是单例的。
(4). 分级容器单例
1 { 2 Console.WriteLine("------------------- 05-Unity的生命周期-分级容器单例(4) -------------------"); 3 //父Unity容器 4 IUnityContainer container = new UnityContainer(); 5 container.RegisterType<IPhone, AndroidPhone>(new HierarchicalLifetimeManager()); 6 //子Unity容器1 7 IUnityContainer childContainer1 = container.CreateChildContainer(); 8 childContainer1.RegisterType<IPhone, AndroidPhone>(new HierarchicalLifetimeManager()); 9 //子Unity容器2 10 IUnityContainer childContainer2 = container.CreateChildContainer(); 11 childContainer2.RegisterType<IPhone, AndroidPhone>(new HierarchicalLifetimeManager()); 12 13 IPhone iphone1 = container.Resolve<IPhone>(); 14 IPhone iphone2 = container.Resolve<IPhone>(); 15 IPhone iphone3 = childContainer1.Resolve<IPhone>(); 16 IPhone iphone4 = childContainer1.Resolve<IPhone>(); 17 IPhone iphone5 = childContainer2.Resolve<IPhone>(); 18 IPhone iphone6 = childContainer2.Resolve<IPhone>(); 19 20 Console.WriteLine("父容器container第一次创建的对象的hashCode值:{0}", iphone1.GetHashCode()); 21 Console.WriteLine("父容器container第二次创建的对象的hashCode值:{0}", iphone2.GetHashCode()); 22 23 Console.WriteLine("子容器childContainer1第一次创建的对象的hashCode值:{0}", iphone3.GetHashCode()); 24 Console.WriteLine("子容器childContainer1第二次创建的对象的hashCode值:{0}", iphone4.GetHashCode()); 25 26 Console.WriteLine("子容器childContainer2第一次创建的对象的hashCode值:{0}", iphone5.GetHashCode()); 27 Console.WriteLine("子容器childContainer2第二次创建的对象的hashCode值:{0}", iphone6.GetHashCode()); 28 29 //总结:unity容器可以创建很多子容器,每个子容器无论怎么创建对象,都是单例的,但是子容器之间不是单例的。 30 //好处:我们可以对于不同生命周期的对象放在不同容器中,如果一个子容器释放,不会影响其它子容器的对象, 31 //但是如果根节点处的父容器被释放,所有的子容器都将被释放 32 }
分析:每个子容器创建的对象的值是相同的,子容器之间创建的对象是不同的
(5). 外部可释放单例
1 { 2 Console.WriteLine("------------------- 05-Unity的生命周期-外部可释放单例(5) -------------------"); 3 IUnityContainer container = new UnityContainer(); 4 container.RegisterType<IPhone, AndroidPhone>(new ExternallyControlledLifetimeManager()); 5 6 IPhone iphone1 = container.Resolve<IPhone>(); 7 IPhone iphone2 = container.Resolve<IPhone>(); 8 Console.WriteLine("第一次调用:{0}", iphone1.GetHashCode()); 9 Console.WriteLine("第二次调用:{0}", iphone2.GetHashCode()); 10 11 Console.WriteLine("------------------ GC回收过后 ------------------------"); 12 iphone1 = iphone2 = null; 13 GC.Collect(); 14 Console.WriteLine("回收后第一次调用:{0}", container.Resolve<IPhone>().GetHashCode()); 15 Console.WriteLine("回收后第二次调用:{0}", container.Resolve<IPhone>().GetHashCode()); 16 17 //总结:在不销毁的情况下,每次Resolve都会返回同一个对象,即是单例的;销毁后,重新创建一个新的对象 18 //销毁后创建的新对象又是单例的。 19 20 21 }
分析:回收前创建的对象都是单例的,回收后重新创建的对象还是单例的。
(6). 循环引用
不推荐使用。
二. 配置文件的形式实现
Unity在实际开发环境中, 注册类型(包括注入对象、声明生命周期)的那一步,都是在配置文件中声明的,这才是Unity的真谛所在,才能实现真正意义上的解耦,只需将Service层的DLL文件复制到主程序bin文件夹中即可,不需要直接添加对Service层的引用。
该实现形式主要分为以下几步:
(1). 编写配置文件的内容。(需要将该配置文件的属性改为“始终复制”,使其可以生成到主程序的bin文件中)。
(2). 固定4行代码读取配置文件。
1 ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap(); 2 fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\UnityConfig.xml");//找配置文件的路径 3 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); 4 UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
(3). 声明Unity容器,并与配置文件关联。
1 IUnityContainer container = new UnityContainer(); 2 section.Configure(container, "testContainer");
(4). Unity解析对象。
(一).配置文件的书写形式
下面整个Xml均为Untiy的配置文件,声明Unity容器有两种方式:①先定义别名,类型名称和程序集名称均写在别名中,然后在容器中与别名进行关联。 ②直接在容器中的register节点写类型名称和程序集名称。
另外可以在register节点中添加<lifetime>节点,可以声明Unity的声明周期。
1 <configuration> 2 <configSections> 3 <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/> 4 </configSections> 5 <unity> 6 <!-- unity容器支持AOP扩展 --> 7 <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"/> 8 9 <!--定义类型别名--> 10 <aliases> 11 <!--type的两个参数分别是:类型名称和DLL程序集的名称--> 12 <!--alias中的别名供container容器中使用--> 13 <add alias="IPhone" type="Ypf.Interface.IPhone,Ypf.Interface" /> 14 <add alias="AndroidPhone" type="Ypf.Service.AndroidPhone, Ypf.Service" /> 15 <add alias="ApplePhone" type="Ypf.Service.ApplePhone, Ypf.Service" /> 16 17 <add alias="IMicrophone" type="Ypf.Interface.IMicrophone, Ypf.Interface" /> 18 <add alias="IHeadphone" type="Ypf.Interface.IHeadphone, Ypf.Interface" /> 19 <add alias="IPower" type="Ypf.Interface.IPower, Ypf.Interface" /> 20 <add alias="Microphone" type="Ypf.Service.Microphone, Ypf.Service" /> 21 <add alias="Headphone" type="Ypf.Service.Headphone, Ypf.Service" /> 22 <add alias="Power" type="Ypf.Service.Power, Ypf.Service" /> 23 </aliases> 24 25 26 <!-- unity容器配置注册节点--> 27 <containers> 28 <!--容器配置方式一:类型名称和程序集名称全部写在容器中--> 29 <container name="testContainer"> 30 <!-- type和mapTo中的两个参数分别是:类型名称和DLL程序集的名称 --> 31 <!-- type和mapTo分别对应RegisterType<A,B> 中的A和B两个值--> 32 <register type="Ypf.Interface.IPhone,Ypf.Interface" mapTo="Ypf.Service.AndroidPhone, Ypf.Service"/> 33 <register type="Ypf.Interface.IPhone,Ypf.Interface" mapTo="Ypf.Service.AndroidPhone, Ypf.Service" name="android"/> 34 <register type="Ypf.Interface.IPhone,Ypf.Interface" mapTo="Ypf.Service.ApplePhone, Ypf.Service" name="apple"/> 35 <!--以下三个属于依赖注入的内容了--> 36 <register type="Ypf.Interface.IMicrophone, Ypf.Interface" mapTo="Ypf.Service.Microphone, Ypf.Service"/> 37 <register type="Ypf.Interface.IHeadphone, Ypf.Interface" mapTo="Ypf.Service.Headphone, Ypf.Service"/> 38 <register type="Ypf.Interface.IPower, Ypf.Interface" mapTo="Ypf.Service.Power, Ypf.Service"/> 39 </container> 40 41 <!--容器配置方式二:配合aliases类型别名进行使用--> 42 <container name="testContainer2"> 43 <!-- type和mapTo中的两个参数分别是:类型名称和DLL程序集的名称 --> 44 <!-- type和mapTo分别对应RegisterType<A,B> 中的A和B两个值--> 45 <register type="IPhone" mapTo="AndroidPhone"/> 46 <register type="IPhone" mapTo="AndroidPhone" name="android"/> 47 <register type="IPhone" mapTo="ApplePhone" name="apple"/> 48 <!--以下三个属于依赖注入的内容了--> 49 <register type="IMicrophone" mapTo="Microphone"/> 50 <register type="IHeadphone" mapTo="Headphone"/> 51 <register type="IPower" mapTo="Power"/> 52 </container> 53 54 <!--在配置文件中配置Unity的生命周期 以AndroidPhone为例--> 55 <container name="testContainer3"> 56 <!-- 下面是用别名的形式, type和mapto分别对应上面的别名 --> 57 <!-- 声明周期放到该节点里面用lifetime进行包裹,不用别名的形式道理一样,也是这种方式进行包裹--> 58 <register type="IPhone" mapTo="AndroidPhone"> 59 <!--1. 瞬时的 默认即为瞬时的--> 60 <!--<lifetime type="TransientLifetimeManager" />--> 61 <!--2. 容器单例的--> 62 <!--<lifetime type="ContainerControlledLifetimeManager" />--> 63 <!--3. 线程单例--> 64 <!--<lifetime type="PerThreadLifetimeManager" />--> 65 <!--4. 分级容器单例--> 66 <lifetime type="hierarchical" /> 67 <!--其它两种不做测试--> 68 </register> 69 </container> 70 </containers> 71 72 </unity> 73 </configuration>
(二). 容器配置方式一:类型名称和程序集名称全部写在容器中
{ //Console.WriteLine("---------------------------容器配置方式一:类型名称和程序集名称全部写在容器中--------------------------------------"); ///*(一). 该配置文件包含依赖注入、指定命名注册*/ ////1. 编写配置文件中的内容(需要将UnityConfig.xml的属性改为始终赋值,使其能生成到bin下) ////2. 固定的4行代码读取配置文件 ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap(); fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\UnityConfig.xml");//找配置文件的路径 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName); ////3. Unity层次的步骤 IUnityContainer container = new UnityContainer(); section.Configure(container, "testContainer"); IPhone iphone1 = container.Resolve<IPhone>(); iphone1.Call(); IPhone iphone2 = container.Resolve<IPhone>("apple"); iphone2.Call(); IPhone iphone3 = container.Resolve<IPhone>("android"); iphone3.Call(); }
(三). 容器配置方式二:配合aliases类型别名进行使用
1 { 2 //Console.WriteLine("---------------------------容器配置方式二:配合aliases类型别名进行使用--------------------------------------"); 3 ///*(一). 该配置文件包含依赖注入、指定命名注册*/ 4 5 //1. 编写配置文件中的内容(需要将UnityConfig.xml的属性改为始终赋值,使其能生成到bin下) 6 //2. 固定的4行代码读取配置文件 7 ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap(); 8 fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\UnityConfig.xml");//找配置文件的路径 9 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); 10 UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName); 11 //3. Unity层次的步骤 12 IUnityContainer container = new UnityContainer(); 13 section.Configure(container, "testContainer2"); 14 15 IPhone iphone1 = container.Resolve<IPhone>(); 16 iphone1.Call(); 17 IPhone iphone2 = container.Resolve<IPhone>("apple"); 18 iphone2.Call(); 19 IPhone iphone3 = container.Resolve<IPhone>("android"); 20 iphone3.Call(); 21 }
(四). Unity的生命周期-容器单例--配置文件的方式 (可以注释配置文件中的节点,来测试其他情况:瞬时、线程单例)
1 Console.WriteLine("------------------- Unity的生命周期-容器单例--配置文件的方式 -------------------"); 2 //1. 编写配置文件中的内容(需要将UnityConfig.xml的属性改为始终赋值,使其能生成到bin下) 3 //2. 固定的4行代码读取配置文件 4 ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap(); 5 fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\UnityConfig.xml");//找配置文件的路径 6 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); 7 UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName); 8 //3. Unity层次的步骤 9 IUnityContainer container = new UnityContainer(); 10 section.Configure(container, "testContainer3"); 11 12 //下面测试多线程中创建的对象是否是单例的(iphone1是一个线程 iphone2和iphone3是同一个线程) 13 IPhone iphone1 = null; 14 Action act1 = () => 15 { 16 iphone1 = container.Resolve<IPhone>(); 17 Console.WriteLine("iphone1由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 18 }; 19 var result1 = act1.BeginInvoke(null, null); 20 IPhone iphone2 = null; 21 Action act2 = () => 22 { 23 iphone2 = container.Resolve<IPhone>(); 24 Console.WriteLine("iphone2由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 25 }; 26 //在act2的异步回调中创建iphone3(iphone2和iphone3是一个线程创建的) 27 IPhone iphone3 = null; 28 var result2 = act2.BeginInvoke(t => 29 { 30 iphone3 = container.Resolve<IPhone>(); 31 Console.WriteLine("iphone3由线程id={0}创建", Thread.CurrentThread.ManagedThreadId); 32 //代表两个不同线程创建的对象 33 Console.WriteLine("iphone1和iphone2是否相等?{0}", object.ReferenceEquals(iphone1, iphone2)); 34 //代表同一个线程创建的两个对象 35 Console.WriteLine("iphone2和iphone3是否相等?{0}", object.ReferenceEquals(iphone2, iphone3)); 36 }, null); 37 38 //线程等待 39 act1.EndInvoke(result1); 40 act2.EndInvoke(result2);
(五). Unity的生命周期-分级容器-配置文件
1 Console.WriteLine("------------------- Unity的生命周期-分级容器--配置文件的方式 -------------------"); 2 //1. 编写配置文件中的内容(需要将UnityConfig.xml的属性改为始终赋值,使其能生成到bin下) 3 //2. 固定的4行代码读取配置文件 4 ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap(); 5 fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\UnityConfig.xml");//找配置文件的路径 6 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); 7 UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName); 8 //3. Unity层次的步骤 9 IUnityContainer container = new UnityContainer(); 10 section.Configure(container, "testContainer3"); 11 12 //子Unity容器1 13 IUnityContainer childContainer1 = container.CreateChildContainer(); 14 15 //子Unity容器2 16 IUnityContainer childContainer2 = container.CreateChildContainer(); 17 18 IPhone iphone1 = container.Resolve<IPhone>(); 19 IPhone iphone2 = container.Resolve<IPhone>(); 20 IPhone iphone3 = childContainer1.Resolve<IPhone>(); 21 IPhone iphone4 = childContainer1.Resolve<IPhone>(); 22 IPhone iphone5 = childContainer2.Resolve<IPhone>(); 23 IPhone iphone6 = childContainer2.Resolve<IPhone>(); 24 25 Console.WriteLine("父容器container第一次创建的对象的hashCode值:{0}", iphone1.GetHashCode()); 26 Console.WriteLine("父容器container第二次创建的对象的hashCode值:{0}", iphone2.GetHashCode()); 27 28 Console.WriteLine("子容器childContainer1第一次创建的对象的hashCode值:{0}", iphone3.GetHashCode()); 29 Console.WriteLine("子容器childContainer1第二次创建的对象的hashCode值:{0}", iphone4.GetHashCode()); 30 31 Console.WriteLine("子容器childContainer2第一次创建的对象的hashCode值:{0}", iphone5.GetHashCode()); 32 Console.WriteLine("子容器childContainer2第二次创建的对象的hashCode值:{0}", iphone6.GetHashCode());
三. Unity实现AOP
待定