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方法来动态创建了一个类型,这些都是反射经常被用来完成的功能,执行结果如下图所示:

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

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

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

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

上图的设计思路是:

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

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

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

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

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

/// <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类中,我们通过工厂管理类根据需要的不同零件类型获取到了不同的产品零件,其运行结果如下图所示:

当一个新的产品—地板需要被添加时,我们需要改的地方是:添加零件枚举记录、添加针对地板的工厂类、添加新地板产品类,修改工厂管理类(在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上的属性初始化参数(增加地板类型),可以看到这时调用者、工厂管理类和工厂都不再需要对新添加的零件进行改动,程序只需要添加必要的类型和枚举记录即可。

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

End总结

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

.NET基础知识快速通关10相关推荐

  1. .NET基础知识快速通关(10)

    [.NET]| 总结/Edison Zhou 本文为第十篇,我们会对.NET的反射相关考点进行基础复习,全文会以Q/A的形式展现,即以面试题的形式来描述. 1能说说反射的基本原理吗? 反射是一种动态分 ...

  2. 黑客术语基础知识快速了解

    黑客术语基础知识快速了解 1.肉鸡:所谓"肉鸡"是一种很形象的比方,比方那些能够随意被我们操控的电脑,对方可所以WINDOWS体系,也可所以UNIX/LINUX体系,可所以一般的个 ...

  3. 网络基础知识 快速计算子网掩码的2种方法

    网络基础知识 快速计算子网掩码的2种方法<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office ...

  4. thinkph 上花院 生多行mysql_PHP_ThinkPHP3.1基础知识快速入门,在当今众多的MVC框架中,ThinkPH - phpStudy...

    ThinkPHP3.1基础知识快速入门 在当今众多的MVC框架中,ThinkPHP是一个快速.简单的基于MVC和面向对象的轻量级PHP开发框架,其遵循Apache2开源协议发布,自从诞生以来一直秉承简 ...

  5. 计算机基础知识ppt课件10页,2019第 1 章 计算机基础知识ppt课件.ppt

    <2019第 1 章 计算机基础知识ppt课件.ppt>由会员分享,提供在线免费全文阅读可下载,此文档格式为ppt,更多相关<2019第 1 章 计算机基础知识ppt课件.ppt&g ...

  6. 【笔记】——MySQL数据库——基础知识-——快速回顾——(加深印象篇)

    文章目录 前言 一.MySQL是什么? 二.数据库的常见概念 三.数据库存储数据的特点 四.SQL语言的分类 1.)DML 2.)DDL 3.)DCL 五.数据库的基本操作 5.1.)创建数据库 5. ...

  7. 计算机概论--计算机基础知识快速入门

    0.前言 1.计算机:辅助人脑的好工具 1.1计算机硬件的五大单元 1.2CPU的种类 1.3接口设备 1.4运作流程 2.个人计算机架构与接口设备 2.1CPU 2.2内存 2.3显卡 2.4硬盘与 ...

  8. JAVA面向对象的基础知识快速通过---自学笔记(一)

    网上的教学很多,看视频非常浪费时间,看文字快点,快速捡起知识点,我只根据我学到的,集各种教学学习,把精华提取出来,把主要概念通俗的展示出来,基本常识就不介绍了,其他的资料谁看了都能看懂,只是java特 ...

  9. JAVA进阶的基础知识快速通过---自学笔记(二)

    温故而知新---陆续学习陆续更新中,你有更好的记忆和学习方法,请在评论区提出来大家一起交流,认真看完,一行行代码备注看完看懂,保证学会,学不会找我. 前言: 代码学完发现很简单,很多初学者搞不明白什么 ...

最新文章

  1. 中文版!Python入门学习的三件法宝!附免费下载
  2. spring 事务 对象保存之后,修改的时候,自动更新提交?
  3. “我是技术总监,你干嘛总问我技术细节?”| 程序员有话说
  4. ES5-8 闭包高级、对象、构造函数、实例化
  5. 本地缓存Caffeine
  6. 实现机器学习的循序渐进指南XI——DBSCAN
  7. 小程序获取用户地址信息api
  8. kingdee kis
  9. P1754 球迷购票问题 (卡特兰数,递推)
  10. pycharm git OpenSSL SSL_read: Connection was reset, errno 10054
  11. redhat enterprise linux5.4.iso,版本有RedHat Enterprise Linux(RHEL)5.4/5.5/5.8/6.0/6.3 ISO镜像文件下载地址...
  12. 【交通标志识别】基于matlab GUI SIFT交通标志识别【含Matlab源码 864期】
  13. 母亲节海报太暖心,速来围观打卡吧~
  14. Nginx的重启命令(nginx -s reopen)
  15. 9个经典营销创业案例,彻底颠覆你的营销思维
  16. 浅谈响应式开发与自适应布局!
  17. 无线节点的空中唤醒技术解析
  18. c语言设计一个程序:计算圆周率,精确到小数点后8位数
  19. 超级电容容量、耐压值、电容值计算方法
  20. Linux驱动开发(一):字符设备

热门文章

  1. div内容超出自动滚动
  2. CSDN如何实现上角标、下角标
  3. Python实现软件自动登录超时锁屏
  4. 图像质量的客观评估指标PSNR与SSIM
  5. python自编码器降维_自编码器数据降维实践
  6. DB2 SQL语句的优化
  7. win7 64打开文件夹总是新窗口打开的解决办法
  8. 三星com.android,三星生活助手(com.samsung.android.app.sreminder) - 7.0.00.9 - 应用 - 酷安
  9. ue4 蓝图设置屏幕百分比_【UE4教程】虚幻4引擎动画在线自学
  10. 深度 | 蔚来的“危险游戏”