文章目录
- 前言
- 一、反射的优缺点
- (一)优点
- (二)缺点
- 二、反射的用途
- (一)查看特性(attribute)信息
- (二)审查集合中的各种类型并实例化
- (三)实现延迟绑定的方法和属性
- (四)在运行时创建新类型并执行任务
- 三、查看元数据实例解析
- (一)通过 MemberInfo 查看自定义特性示例
- (二)通过反射读取类及其成员的自定义特性示例
前言
反射是 C# 中一种强大且独特的机制,它赋予程序访问、检测以及修改其本身状态或行为的能力。从程序的结构角度来看,程序集是一个包含了多个模块的集合,模块之中又包含了各式各样的类型,而类型进一步包含了诸多成员,如字段、属性、方法等。反射机制则通过提供一系列的对象,对程序集、模块以及类型进行了有效的封装,使得开发者可以在运行时深入探究程序的内部结构并进行相应操作。
一、反射的优缺点
(一)优点
- 增强程序灵活性与扩展性: 在实际的软件开发中,需求常常会发生变化。通过反射,程序可以根据不同的运行时条件,动态地去适应这些变化,无需对代码进行大规模的重写。例如在设计一个支持多种数据库连接的应用程序时,利用反射可以根据配置文件中指定的数据库类型,动态加载相应的数据库驱动类并建立连接,轻松应对后续可能新增的数据库类型支持需求,大大提升了程序应对变化的能力。
- 降低耦合性: 在面向对象编程中,耦合度过高会使得代码的维护和扩展变得困难。反射允许程序创建和控制任何类的对象,而无需提前在代码中硬编码目标类的相关信息。这样一来,不同模块之间的依赖关系可以变得更加松散,比如在一个游戏开发中,不同的游戏角色类可以通过反射机制在需要的时候被动态加载和使用,而不是在各个模块中都显式地引用它们,从而提高了整个系统的自适应能力。
(二)缺点
- 性能问题: 反射操作本质上是一种解释性的操作,当使用它来访问字段或者调用方法时,其执行速度相较于直接编写代码进行相应操作要慢得多。这是因为反射需要在运行时去解析类型信息、查找成员等额外的开销。正因为如此,反射机制主要适用于那些对灵活性和扩展性要求极高的系统框架开发场景中,像一些普通的、对性能较为敏感的应用程序,一般不建议大量使用反射,以免影响整体的运行效率。
- 逻辑模糊与维护困难: 程序员在阅读和理解源代码时,往往希望能够清晰地看到程序的逻辑走向。然而,反射机制绕过了常规源代码所体现的直接逻辑,使得代码的理解难度增加。反射代码本身相对来说也比直接编写的对应代码更加复杂,这在后续的维护过程中就容易造成困扰,比如在排查问题或者进行功能扩展时,需要花费更多的时间和精力去理清反射相关代码的逻辑。
二、反射的用途
(一)查看特性(attribute)信息
在 C# 中,特性是一种可以为代码元素(如类、方法、属性等)添加额外元数据的方式。而反射允许我们在运行时查看这些特性信息,这对于很多基于元数据驱动的开发场景非常关键。例如在进行代码的自动化测试框架设计时,可以通过反射获取方法上标注的测试相关特性(如测试用例的优先级、预期结果等),进而根据这些信息来组织和执行测试用例。
(二)审查集合中的各种类型并实例化
在处理一些包含多种类型对象的集合或者插件式架构时,反射可以帮助我们审查集合中存在的不同类型,然后根据需要实例化这些类型。比如在一个图形绘制系统中,有多种图形类(圆形、矩形、三角形等)都实现了一个统一的图形接口,通过反射可以扫描指定的程序集,找到所有实现该接口的类型,然后根据用户选择动态实例化相应的图形对象进行绘制。
(三)实现延迟绑定的方法和属性
延迟绑定意味着在运行时才决定要调用哪个对象的哪个方法或者访问哪个属性。反射提供了这样的能力,使得程序可以根据实时的业务逻辑或者用户输入来动态决定操作的具体目标。例如在一个动态表单生成系统中,根据用户配置的表单字段类型和对应的操作逻辑,通过反射在运行时绑定到相应的数据处理方法和属性,实现灵活的数据交互。
(四)在运行时创建新类型并执行任务
有时候,我们可能需要在程序运行过程中动态地创建一些新的类型,反射可以帮助我们达成这一目标。例如在代码生成工具或者动态代理类的创建场景中,利用反射能够根据特定的规则和模板在运行时生成新的类型,并且使用这些新创建的类型去完成诸如数据封装、接口代理等各种任务。
三、查看元数据实例解析
(一)通过 MemberInfo 查看自定义特性示例
在 C# 中,要查看与类相关的特性信息,首先需要初始化 System.Reflection 类的 MemberInfo 对象。以下是一个简单的示例代码来说明这一过程:
using System;[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute : System.Attribute
{public readonly string Url;public string Topic // Topic 是一个命名(named)参数{get{return topic;}set{topic = value;}}public HelpAttribute(string url) // url 是一个定位(positional)参数{this.Url = url;}private string topic;
}
[HelpAttribute("Information on the class MyClass")]
class MyClass
{
}namespace AttributeAppl
{class Program{static void Main(string[] args){System.Reflection.MemberInfo info = typeof(MyClass);object[] attributes = info.GetCustomAttributes(true);for (int i = 0; i < attributes.Length; i++){System.Console.WriteLine(attributes[i]);}Console.ReadKey();}}
}
在上述代码中:
首先定义了一个自定义特性 HelpAttribute,它有相应的属性(如 Url、Topic)以及构造函数。然后将这个特性应用到 MyClass 类上。在 Main 方法中,通过 typeof(MyClass) 获取 MyClass 对应的 MemberInfo 对象,接着调用 GetCustomAttributes(true) 方法获取该类上的所有自定义特性,并通过循环将它们打印出来。当代码被编译和执行时,就会显示附加到 MyClass 类上的自定义特性,这里会输出 HelpAttribute。
(二)通过反射读取类及其成员的自定义特性示例
下面再来看一个更复杂一些的例子,展示如何使用反射来读取类及其成员中的元数据(以 Rectangle 类和 DeBugInfo 特性为例):
using System;
using System.Reflection;namespace BugFixApplication
{// 一个自定义特性 BugFix 被赋给类及其成员[AttributeUsage(AttributeTargets.Class |AttributeTargets.Constructor |AttributeTargets.Field |AttributeTargets.Method |AttributeTargets.Property,AllowMultiple = true)]public class DeBugInfo : System.Attribute{private int bugNo;private string developer;private string lastReview;public string message;public DeBugInfo(int bg, string dev, string d){this.bugNo = bg;this.developer = dev;this.lastReview = d;}public int BugNo{get{return bugNo;}}public string Developer{get{return developer;}}public string LastReview{get{return lastReview;}}public string Message{get{return message;}set{message = value;}}}[DeBugInfo(45, "Zara Ali", "12/8/2012",Message = "Return type mismatch")][DeBugInfo(49, "Nuha Ali", "10/10/2012",Message = "Unused variable")]class Rectangle{// 成员变量protected double length;protected double width;public Rectangle(double l, double w){length = l;width = w;}[DeBugInfo(55, "Zara Ali", "19/10/2012",Message = "Return type mismatch")]public double GetArea(){return length * width;}[DeBugInfo(56, "Zara Ali", "19/10/2012")]public void Display(){Console.WriteLine("Length: {0}", length);Console.WriteLine("Width: {0}", width);Console.WriteLine("Area: {0}", GetArea());}}//end class Rectangle class ExecuteRectangle{static void Main(string[] args){Rectangle r = new Rectangle(4.5, 7.5);r.Display();Type type = typeof(Rectangle);// 遍历 Rectangle 类的特性foreach (Object attributes in type.GetCustomAttributes(false)){DeBugInfo dbi = (DeBugInfo)attributes;if (null!= dbi){Console.WriteLine("Bug no: {0}", dbi.BugNo);Console.WriteLine("Developer: {0}", dbi.Developer);Console.WriteLine("Last Reviewed: {0}",dbi.LastReview);Console.WriteLine("Remarks: {0}", dbi.Message);}}// 遍历方法特性foreach (MethodInfo m in type.GetMethods()){foreach (Attribute a in m.GetCustomAttributes(true)){DeBugInfo dbi = (DeBugInfo)a;if (null!= dbi){Console.WriteLine("Bug no: {0}, for Method: {1}",dbi.BugNo, m.Name);Console.WriteLine("Developer: {0}", dbi.Developer);Console.WriteLine("Last Reviewed: {0}",dbi.LastReview);Console.WriteLine("Remarks: {0}", dbi.Message);}}}Console.ReadLine();}}
}
在这个示例中:
首先定义了 DeBugInfo 特性,它用于记录调试相关的信息,如错误编号、开发者、最后审查时间以及备注信息等,并且通过 AttributeUsage 特性指定了它可以应用的目标元素范围以及允许重复应用。
接着在 Rectangle 类及其部分方法上应用了 DeBugInfo 特性,用于标记不同位置的调试相关元数据。
在 Main 方法中:
先创建了 Rectangle 类的实例并调用 Display 方法展示基本的矩形信息。
然后通过 typeof(Rectangle) 获取 Rectangle 类对应的 Type 对象,利用这个对象先遍历类级别的自定义特性,将每个 DeBugInfo 特性中的相关信息打印出来。
之后再通过 GetMethods 方法获取 Rectangle 类的所有方法,进一步遍历每个方法上的自定义特性,同样将其中的关键调试信息打印输出。
当代码被编译和执行后,就会按照顺序输出矩形的基本信息以及类和方法上标注的 DeBugInfo 特性所包含的详细调试信息,清晰地展示了通过反射获取类及其成员元数据的完整过程。