Skip to main content
 Web开发网 » 站长学院 » 浏览器插件

面试 | .NET基础知识快速通关(10)

2021年10月11日6750百度已收录

面试 | .NET基础知识快速通关(10)  .net面试经验 第1张

【.NET】| 总结/Edison Zhou

本文为第十篇,我们会对.NET的反射相关考点进行基础复习,全文会以Q/A的形式展现,即以面试题的形式来描述。

1 能说说反射的基本原理吗?

反射是一种动态分析程序集、模块、类型及字段等目标对象的机制,它的实现依托于元数据。

元数据,就是描述数据的数据。

在CLR中,元数据就是对一个模块定义或引用的所有东西的描述系统。

2 .NET中如何实现反射?

在.NET中,为我们提供了丰富的可以用来实现反射的类型,这些类型大多数都定义在System.Reflection命名空间之下,例如Assembly、Module等。利用这些类型,我们就可以方便地动态加载程序集、模块、类型、方法和字段等元素。

下面我们来看一个使用示例,首先是创建一个程序集SimpleAssembly,其中有一个类为SimpleClass:

[Serializable]public class SimpleClass{ private String _MyString; public SimpleClass(String mystring) { _MyString = mystring; } public override string ToString() { return _MyString; } static void Main(string[] args) { Console.WriteLine("简单程序集"); Console.Read(); }}其次是对程序集中的模块进行分析,分别利用反射对程序集、模块和类进行分析:

public class AnalyseHelper{ /// <summary> /// 分析程序集 /// </summary> public static void AnalyzeAssembly(Assembly assembly) { Console.WriteLine("程序集名字:" + assembly.FullName); Console.WriteLine("程序集位置:" + assembly.Location); Console.WriteLine("程序集是否在GAC中:" + assembly.GlobalAssemblyCache.ToString()); Console.WriteLine("包含程序集的模块名" + assembly.ManifestModule.Name); Console.WriteLine("运行程序集需要的CLR版本:" + assembly.ImageRuntimeVersion); Console.WriteLine("现在开始分析程序集中的模块"); Module[] modules = assembly.GetModules(); foreach (Module module in modules) { AnalyzeModule(module); } } /// <summary> /// 分析模块 /// </summary> public static void AnalyzeModule(Module module) { Console.WriteLine("模块名:" + module.Name); Console.WriteLine("模块的UUID:" + module.ModuleVersionId); Console.WriteLine("开始分析模块下的类型"); Type[] types = module.GetTypes(); foreach (Type type in types) { AnalyzeType(type); } } /// <summary> /// 分析类型 /// </summary> public static void AnalyzeType(Type type) { Console.WriteLine("类型名字:" + type.Name); Console.WriteLine("类型的类别是:" + type.Attributes); if (type.BaseType != null) Console.WriteLine("类型的基类是:" + type.BaseType.Name); Console.WriteLine("类型的GUID是:" + type.GUID); //设置感兴趣的类型成员 BindingFlags flags = (BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); //分析成员 FieldInfo[] fields = type.GetFields(flags); if (fields.Length > 0) { //Console.WriteLine("开始分析类型的成员"); foreach (FieldInfo field in fields) { // 分析成员 } } //分析包含的方法 MethodInfo[] methods = type.GetMethods(flags); if (methods.Length > 0) { //Console.WriteLine("开始分析类型的方法"); foreach (MethodInfo method in methods) { // 分析方法 } } //分析属性 PropertyInfo[] properties = type.GetProperties(flags); if (properties.Length > 0) { //Console.WriteLine("开始分析类型的属性"); foreach (PropertyInfo property in properties) { // 分析属性 } } }}最后编写入口方法来尝试分析一个具体的程序集:

[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]public class Program{ public static void Main(string[] args) { Assembly assembly = Assembly.LoadFrom(@"..\..\..\SimpleAssembly\bin\Debug\SimpleAssembly.exe"); AnalyseHelper.AnalyzeAssembly(assembly); // 创建一个程序集中的类型的对象 Console.WriteLine("利用反射创建对象"); string[] paras = { "测试一下反射效果" }; object obj = assembly.CreateInstance(assembly.GetModules()[0].GetTypes()[0].ToString(), true, BindingFlags.CreateInstance, null, paras, null, null); Console.WriteLine(obj); Console.ReadKey(); }}上面的代码按照 程序集->模块->类型 三个层次的顺序来动态分析一个程序集,当然还可以继续递归类型内部的成员,最后通过CreateInstance方法来动态创建了一个类型,这些都是反射经常被用来完成的功能,执行结果如下图所示:

面试 | .NET基础知识快速通关(10)  .net面试经验 第2张

3 如何用反射实现工厂模式?

工厂模式是一种比较常用的设计模式,其基本思想在于使用不同的工厂类型来打造不同产品的部件。例如,我们在打造一间屋子时,可能需要窗户、屋顶、门、房梁、柱子等零部件。有的屋子需要很多根柱子,而有的屋子又不需要窗户。在这样的需求下,就可以使用工厂模式。

(1)工厂模式的传统实现和其弊端

下图展示了针对屋子设计的传统工厂模式架构图:

面试 | .NET基础知识快速通关(10)  .net面试经验 第3张

上图的设计思路是:

① 使用者告诉工厂管理者需要哪个产品部件;

② 工厂管理者分析使用者传入的信息,生成合适的实现工厂接口的类型对象;

③ 通过工厂生产出相应的产品,返回给使用者一个实现了该产品接口的类型对象;

通过上述思路,实现代码如下:

① 首先是定义工厂接口,产品接口与产品类型的枚举

/// <summary>/// 屋子产品的零件/// </summary>public enum RoomParts{ Roof, Window, Pillar}/// <summary>/// 工厂接口/// </summary>public interface IFactory{ IProduct Produce();}/// <summary>/// 产品接口/// </summary>public interface IProduct{ string GetName();}② 其次是具体实现产品接口的产品类:窗户、屋顶和柱子

/// <summary>/// 屋顶/// </summary>public class Roof : IProduct{ // 实现接口,返回产品名字 public string GetName() { return "屋顶"; }}/// <summary>/// 窗户/// </summary>public class Window : IProduct{ // 实现接口,返回产品名字 public string GetName() { return "窗户"; }}/// <summary>/// 柱子/// </summary>public class Pillar : IProduct{ // 实现接口,返回产品名字 public string GetName() { return "柱子"; }}③ 然后是具体实现工厂接口的工厂类:实现接口返回一个具体的产品对象

/// <summary>/// 屋顶工厂/// </summary>public class RoofFactory : IFactory{ // 实现接口,返回一个产品对象 public IProduct Produce() { return new Roof(); }}/// <summary>/// 窗户工厂/// </summary>public class WindowFactory : IFactory{ // 实现接口,返回一个产品对象 public IProduct Produce() { return new Window(); }}/// <summary>/// 柱子工厂/// </summary>public class PillarFactory : IFactory{ // 实现接口,返回一个产品对象 public IProduct Produce() { return new Pillar(); }}④ 最后是工厂管理类:组织起众多的产品与工厂

/// <summary>/// 工厂管理者/// </summary>public class FactoryManager{ public static IProduct GetProduct(RoomParts part) { IFactory factory = null; // 传统工厂模式的弊端:工厂管理类和工厂类族的紧耦合 switch (part) { case RoomParts.Roof: factory = new RoofFactory(); break; case RoomParts.Window: factory = new WindowFactory(); break; case RoomParts.Pillar: factory = new PillarFactory(); break; default: return null; } // 利用工厂生产产品 IProduct product = factory.Produce(); Console.WriteLine("生产了一个产品:{0}", product.GetName()); return product; }}按照国际惯例,我们实现一个入口方法来测试一下:

public class Customer{ public static void Main(string[] args) { // 根据需要获得不同的产品零件 IProduct window = FactoryManager.GetProduct(RoomParts.Window); Console.WriteLine("我获取到了{0}",window.GetName()); IProduct roof = FactoryManager.GetProduct(RoomParts.Roof); Console.WriteLine("我获取到了{0}", roof.GetName()); IProduct pillar = FactoryManager.GetProduct(RoomParts.Pillar); Console.WriteLine("我获取到了{0}", pillar.GetName()); Console.ReadKey(); }}在Customer类中,我们通过工厂管理类根据需要的不同零件类型获取到了不同的产品零件,其运行结果如下图所示:

面试 | .NET基础知识快速通关(10)  .net面试经验 第4张

当一个新的产品—地板需要被添加时,我们需要改的地方是:添加零件枚举记录、添加针对地板的工厂类、添加新地板产品类,修改工厂管理类(在switch中添加一条case语句),这样设计的优点在于无论添加何种零件,产品使用者都不需要关心内部的变动,可以一如既往地使用工厂管理类来得到希望的零件,而缺点也有以下几点:

① 工厂管理类和工厂类族耦合;

② 每次添加新的零件都需要添加一对工厂类和产品类,类型会越来越多;

(2)基于反射的工厂模式的实现

利用反射机制可以实现更加灵活的工厂模式,这一点体现在利用反射可以动态地获知一个产品由哪些零部件组成,而不再需要用一个switch语句来逐一地寻找合适的工厂。

① 产品、枚举和以上一致,这里的改变主要在于添加了两个自定义的特性,这两个特性会被分别附加在产品类型和产品接口上:

/// <summary>/// 该特性用于附加在产品类型之上/// </summary>[AttributeUsage(AttributeTargets.Class)]public class ProductAttribute : Attribute{ // 标注零件的成员 private RoomParts myRoomPart; public ProductAttribute(RoomParts part) { myRoomPart = part; } public RoomParts RoomPart { get { return myRoomPart; } }}/// <summary>/// 该特性用于附加在产品接口类型之上/// </summary>[AttributeUsage(AttributeTargets.Interface)]public class ProductListAttribute : Attribute{ // 产品类型集合 private Type[] myList; public ProductListAttribute(Type[] products) { myList = products; } public Type[] ProductList { get { return myList; } }}② 下面是产品接口和产品类族的定义,其中产品接口使用了ProductListAttribute特性,而每个产品都使用了ProductAttribute特性:

/// <summary>/// 产品接口/// </summary>[ProductList(new Type[] { typeof(Roof), typeof(Window), typeof(Pillar) })]public interface IProduct{ string GetName();}/// <summary>/// 屋顶/// </summary>[Product(RoomParts.Roof)]public class Roof : IProduct{ // 实现接口,返回产品名字 public string GetName() { return "小天鹅屋顶"; }}/// <summary>/// 窗户/// </summary>[Product(RoomParts.Window)]public class Window : IProduct{ // 实现接口,返回产品名字 public string GetName() { return "双汇窗户"; }}/// <summary>/// 柱子/// </summary>[Product(RoomParts.Pillar)]public class Pillar : IProduct{ // 实现接口,返回产品名字 public string GetName() { return "小米柱子"; }}③ 下面是修改后的工厂类,由于使用了反射特性,这里一个工厂类型就可以生产所有的产品:

/// <summary>/// 工厂类/// </summary>public class Factory{ public IProduct Produce(RoomParts part) { // 通过反射从IProduct接口中获得属性从而获得所有产品列表 ProductListAttribute attr = (ProductListAttribute)Attribute.GetCustomAttribute(typeof(IProduct), typeof(ProductListAttribute)); // 遍历所有的实现产品零件类型 foreach (var type in attr.ProductList) { // 利用反射查找其属性 ProductAttribute pa = (ProductAttribute)Attribute.GetCustomAttribute(type, typeof(ProductAttribute)); // 确定是否是需要到的零件 if(pa.RoomPart == part) { // 利用反射动态创建产品零件类型实例 object product = Assembly.GetExecutingAssembly().CreateInstance(type.FullName); return product as IProduct; } } return null; }}④ 最后是修改后的工厂管理类,核心只有三行代码:

/// <summary>/// 工厂管理者/// </summary>public class FactoryManager{ public static IProduct GetProduct(RoomParts part) { // 一共只有一个工厂 Factory factory = new Factory(); IProduct product = factory.Produce(part); Console.WriteLine("生产了一个产品:{0}", product.GetName()); return product; }}上述代码中最主要的变化在于两点:

其一是工厂管理类不再需要根据不同的零件寻找不同的工厂,因为只有一个工厂负责处理所有的产品零件;

其二是产品类型和产品接口应用了两个自定义特性,来方便工厂进行反射。ProductAttribute附加在产品类上,标注了当前类型代表了哪个产品零件。而ProductListAttribute则附加在产品接口之上,方便反射得知一共有多少产品零件。

这时需要添加一个新的地板产品零件类型时,我们需要做的是:添加零件枚举记录,添加代表地板的类型,修改添加在IProduct上的属性初始化参数(增加地板类型),可以看到这时调用者、工厂管理类和工厂都不再需要对新添加的零件进行改动,程序只需要添加必要的类型和枚举记录即可。

当然,这样的设计也存在一定缺陷:反射的运行效率相对较低,在产品零件相对较多时,每生产一个产品就需要反射遍历这是一件相当耗时的工作。

总结

本文总结复习了.NET的反射(Reflection)相关的重要知识点,下一篇会总结.NET中特性(Attribute)相关的重要知识点,欢迎继续关注!

参考资料(全是经典)

朱毅,《进入IT企业必读的200个.NET面试题》

张子阳,《.NET之美:.NET关键技术深入解析》

王涛,《你必须知道的.NET(第二版)》

评论列表暂无评论
发表评论
微信