在编程世界中,动态操作对象的能力是一个强大而灵活的工具。本文将深入探讨两个关键概念:反射(Reflection)和动态关键字(dynamic)。许多开发者对这两个概念感到困惑,因为它们都能实现动态调用。本文旨在揭示它们之间的区别,并探讨在何种场景下使用它们。但在比较之前,先分别理解每一个概念,然后在文章的最后进行全面比较。
当想要确定或检查一个程序集的内容时,就需要使用反射。例如,当在Visual Studio编辑器中输入对象前的“.”(点)时,它会给显示对象的所有成员。这正是反射的作用。反射还可以进一步操作,它可以在运行时调用检查到的成员。例如,如果反射检测到对象中有一个名为GetChanges的方法,可以在运行时获取该方法的引用并调用它。
简而言之,反射包括两个步骤:“检查”和“调用”(可选)。“调用”过程是可选的。
在C#中实现反射是一个两步过程:首先获取对象的“类型”,然后使用该类型浏览成员,如“方法”、“属性”等。
第一步是获取对象的类型。例如,有一个名为ClassLibrary1.dll的DLL,其中包含一个名为Class1的类。可以使用属于System.Reflection命名空间的Assembly类来获取对象类型的引用。之后,可以使用Activator.CreateInstance来创建类的实例。GetType()函数帮助获取对象类型的引用。
var myAssembly = Assembly.LoadFile("C:\\ClassLibrary1.dll");
var myType = myAssembly.GetType("ClassLibrary1.Class1");
dynamic objMyClass = Activator.CreateInstance(myType);
// 获取类类型
Type parameterType = objMyClass.GetType();
一旦有了对象类型的引用,就可以调用GetMembers()或GetProperties()来浏览类的成员。
// 浏览成员
foreach (MemberInfo objMemberInfo in parameterType.GetMembers())
{
Console.WriteLine(objMemberInfo.Name);
}
// 浏览属性
foreach (PropertyInfo objPropertyInfo in parameterType.GetProperties())
{
Console.WriteLine(objPropertyInfo.Name);
}
如果想调用已经检查过的成员,可以使用InvokeMember来调用方法。以下是代码:
parameterType.InvokeMember("Display", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance, null, objMyClass, null);
如果正在创建一个像Visual Studio编辑器这样的应用程序,希望使用智能感知显示对象的内部结构。如果正在创建一个单元测试框架。在单元测试框架中,需要动态地调用方法和属性以进行测试。有时希望将属性、方法和程序集引用转储到文件中或显示在屏幕上。
编程语言可以分为两类:强类型和动态类型。强类型语言是在编译时进行检查的,而动态语言是在编译时绕过类型检查的。在动态语言中,对象类型只有在运行时才知道,类型检查只在运行时激活。
希望同时利用这两个世界的优势。因为很多时候,直到代码执行时才知道对象类型。换句话说,正在寻找一种动态和静态类型环境的结合。这就是动态关键字帮助地方。
如果使用动态关键字创建一个变量,如果尝试查看该对象的成员,将得到以下消息:“将在运行时解析”。
dynamic x = "c#";
x++;
现在这段代码将在没有任何抱怨的情况下编译。但在运行时,它将抛出一个异常,抱怨不能在变量上执行数学操作,因为它是一个字符串类型。换句话说,在运行时,动态对象从一般数据类型转换为特定数据类型(例如:下面的代码中的字符串)。
动态关键字最大的实际用途之一是通过互操作操作MS Office组件时。例如,如果不使用动态关键字访问Microsoft Excel组件,可以看到代码变得多么复杂。下面有很多强制转换,对吧?
Application excelApplication = new Application();
((Excel.Range)excelApp.Cells[1, 1]).Value2 = "Name";
Excel.Range range2008 = (Excel.Range)excelApp.Cells[1, 1];
现在看看使用动态关键字后代码变得多么简单。不需要强制转换,运行时类型检查也发生了。
dynamic excelApp = new Application();
excelApp.Cells[1, 1].Value = "Name";
Excel.Range range2010 = excelApp.Cells[1, 1];
当想要在运行时操作对象时,都会使用反射和动态。反射用于检查对象的元数据。它还可以在运行时调用对象的成员。动态关键字是在.NET 4.0中引入的。它在运行时评估对象调用。所以在方法调用之前,编译器并不关心这些方法/属性是否存在。
动态使用反射内部。它缓存了所做的方法调用,从而在一定程度上提高了性能。反射可以调用对象的公共和私有成员,而动态只能调用公共成员。动态是实例特定的:无法访问静态成员;在这些情况下,必须使用反射。
下面是一个详细的比较表,显示了它们在哪种场景下适用:
反射 | 动态 |
---|---|
检查(元数据) | 否 |
调用公共成员 | 是 |
调用私有成员 | 否 |
缓存 | 是 |
静态类 | 否 |
以下是一个简单的图表,总结了反射能做什么以及动态关键字能做什么。
对于进一步阅读,请观看以下面试准备视频和逐步视频系列: