.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 AMD64MSIL,当然还有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 等。

可以以字符串获取编译程序中的编程元素, 这可以使我们的程序变得非常灵活, 许多在编译时要确定下来的事情可以放到运行时再做, 要知道在编程世界中,

延迟思想是非常有用的,俗话说,能晚点做就晚点做,这样可以使做的事情更通用, 易于稳定扩展。

 

posted @ 2020-08-04 22:45  我是张志淼  阅读(121)  评论(0)    收藏  举报