.NET开发技术栈: 反射 Reflection
反射,程序员的快乐
C#中的反射是通过微软提供的 System.Reflection这个类库,可以让我们在运行时读取编译后的程序集(dll, exe之类的东西)中的 类型,属性,字段等几乎一切编程元素。
为什么要使用反射
C#是一门强类型语言, 常规操作下, 所有变量在使用前必须先明确其类型,例如:
IAnimal cat= new Cat();
这么玩, 就创建了一直猫! 但这有一个问题, 就是代码写死了。 假设这样的场景: 我需要创建一些动物,但具体是什么动物是从调用端传进来的,换句话说, 是在程序运行时决定的, 有可能是前面程序逻辑计算后得出的结果,
也有可能是从配置文件或数据库中读取出来的。
public Animal GetAnimal(string animal_type) { return ? }
这要怎么写呢, 像上面创建Cat那样肯定是不行的, 因为需要创建的类型是由上端传进来的, 程序编译的时候我们还不知道呢。
反射可以搞定, 因为只要可以读取程序集, 知道类型的字符串名字, 我们就可以获取到具体的类型, 然后用这个类型创建需要的对象, 代码如下
using System; using System.Reflection; namespace ReflectionSample { public abstract class Animal { public string name { get; set; } public abstract void Bark(); } public class Cat : Animal { public override void Bark() { Console.WriteLine("眯眯眯"); } } public class Dot : Animal { public override void Bark() { Console.WriteLine("汪汪汪"); } } public class AnimalShop { public static Animal GetAnimal(string animal_type) { Assembly assembly = Assembly.GetExecutingAssembly(); //获取当前程序集 Type type = assembly.GetType("ReflectionSample." + animal_type); //根据传入的字符串获取类型, 类型名需要是带命名空间的全名称 Animal animal = Activator.CreateInstance(type) as Animal; //根据类型创建实例并转换为Animal类型 return animal; } } } //测试方法 static void Main(string[] args) { string animal_type ="Cat"; Animal animal = AnimalShop.GetAnimal(animal_type); animal.Bark(); }
运行后,Cat创建成功, 并输出了"咪咪咪"。
知道了目标类型,方法,字段等编程元素的字符串名称,我们就能轻易获取到它, 真的方便。
上面讲了这么多, 就为了证明一点 : 反射是很有用的。
相信反射是个好东西后,接下来就差具体各种使用了, 以下是反射的简要说明书
加载程序集的几种方式
1. Assembly.Load()
简介
Load()方法接收一个String或AssemblyName类型作为参数,这个参数实际上是需要加载的程序集的强名称(名称,版本,语言,公钥标记)。例如.NET 2.0中的FileIOPermission类,它的强名称是:
System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
对于弱命名的程序集,则只会有程序集名称,而不会有版本,语言和公钥标记。如 TestClassLibrary
细节
- CLR内部普遍使用了Load()方法来加载程序集,在Load()方法的内部,CLR首先会应用这个程序集的版本绑定重定向策略,接着在GAC中查找目标程序集。如果GAC中没有找到,则会在应用程序目录和子目录中寻找(应用配置文件的codebase所指定的位置)。
- 如果希望加载弱命名程序集,Load()方法就不会去GAC中查找。
- 当Load()找到目标程序集时,就会加载它,并返回一个相应Assembly对象的引用。
- 当没有找到程序集时,会抛出System.IO.FileNotFoundException异常。
- 当存在特定CPU架构的程序集时,CLR会优先加载当前架构的程序集(例如x86版本优先于IL中立版本)
- 如果希望强迫加载某个架构版本的程序集,需要在强名称中加以指定。ProcessorArchitecture可以为x86 IA64 AMD64或MSIL,当然还有None
- Load方法与Win32函数中的LoadLibrary方法等价
2. Assembly.LoadFrom()
简介
LoadFrom()方法可以从指定文件中加载程序集,通过查找程序集的AssemblyRef元数据表,得知所有引用和需要的程序集,然后在内部调用Load()方法进行加载。
Assembly.LoadFrom(@"C:\ABC\Test.dll");
细节
- LoadFrom()首先会打开程序集文件,通过GetAssemblyName方法得到程序集名称,然后关闭文件,最后将得到的AssemblyName对象传入Load()方法中
- 随后,Load()方法会再次打开这个文件进行加载。所以,LoadFrom()加载一个程序集时,会多次打开文件,造成了效率低下的现象(与Load相比)。
- 由于内部调用了Load(),所以LoadFrom()方法还是会应用版本绑定重定向策略,也会在GAC和各个指定位置中进行查找。
- LoadFrom()会直接返回Load()的结果——一个Assembly对象的引用。
- 如果目标程序集已经加载过,LoadFrom()不会重新进行加载。
- LoadFrom支持从一个URL加载程序集(如"http://www.abc.com/test.dll"),这个程序集会被下载到用户缓存文件夹中。
- 从URL加载程序集时,如果计算机未联网,LoadFrom会抛出一个异常。如果IE被设置为“脱机工作”,则不会抛出异常,转而从缓存中寻找已下载的文件。
3. Assembly.LoadFile()
简介
LoadFile()从一个指定文件中加载程序集,它和LoadFrom()的不同之处在于LoadFile()不会加载目标程序集所引用和依赖的其他程序集。您需要自己控制并显示加载所有依赖的程序集
细节
- LoadFile()不会解析任何依赖
- LoadFile()可以多次加载同一程序集
- 显式加载依赖程序集的方法是,注册AppDomain的AssemblyResolve事件
(以上加载程序集的说明摘自 https://www.cnblogs.com/zagelover/articles/2726034.html )
获取Type,为所欲为
拿到assembly后我们可以通过它的 GetType 或者GetTypes方法获取我们所需的Type。
//获取单个特定Type Type type = assembly.GetType("ReflectionSample.Cat"); //获取所有Type Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
}
1. 创建实例
object obj = Activator.CreateInstance(type) ;// 这里有很多重载,请自行尝试 Animal animal = obj as Animal;
2. 获取字段,属性
// type.GetField()是访问所有字段,也可以传入名称访问特定字段或属性
foreach (var item in type.GetProperties())
{
var name = item.Name; //获取属性名称
var value = item.GetValue(object_instance); //获取属性值
item.SetValue(object_instance, value); //设置属性值
}
3. 获取方法并调用
//定义一个类,里面有个Show方法
public class TestClass { public string Show(string name) { return $"Hello {name} "; } } //测试调用Show方法 var type = typeof(TestClass); var obj = Activator.CreateInstance(type); var method = type.GetMethod("Show"); var result = method.Invoke(obj,new object[] {"Beck" }); Console.WriteLine(result);
4. 反射泛型元素
获取type后 , 通过MakeGenericType 获取此泛型类指定类型后的副本
获取method后, 通过MakeGenericMethod获取此泛型反方指定类型后的副本
Assembly assembly = Assembly.LoadFrom("Test.dll"); Type type = assembly.GetType("Zhaoxi.AspNetCore.DB.SqlServer.GenericDouble`1"); Type type1 = type.MakeGenericType(new Type[] { typeof(int) }); var obj = Activator.CreateInstance(type1); MethodInfo show = type1.GetMethod("Show"); MethodInfo show1 = show.MakeGenericMethod(new Type[] { typeof(string), typeof(DateTime) }); show1.Invoke(obj,new object[] { "Beck",DateTime.Now}); show1.Invoke(obj, new object[] { 123,"Beck", DateTime.Now });
总结
现在的.NET程序中, 反射可以说是无处不在,比如各种ORM框架,IOC框架,.NET MVC 等。
可以以字符串获取编译程序中的编程元素, 这可以使我们的程序变得非常灵活, 许多在编译时要确定下来的事情可以放到运行时再做, 要知道在编程世界中,
延迟思想是非常有用的,俗话说,能晚点做就晚点做,这样可以使做的事情更通用, 易于稳定扩展。
浙公网安备 33010602011771号