反射 Reflection
反射:你给我一个对象,我能在不用 new 操作符也不知道该对象的静态类型的情况下,我能给你创建出一个同类型的对象,还能访问该对象的各个成员。
这相当于进一步的解耦,因为有 new 操作符后面总得跟类型,一跟类型就有了紧耦合的依赖。依靠反射创建类型不再需要 new 操作符也无需静态类型,这样使得很多时候耦合可以松到忽略不计。
反射不是 C# 语言的功能,而是 .NET 框架的功能,所以 .NET 框架支持的语言都能使用反射功能。
C# 和 Java 这种托管类型的语言和 C、C++ 这些原生类型的语言区别之一就是反射。
单元测试、依赖注入、泛型编程都是基于反射机制的
本来反射应该是个很复杂的东西,但因为 .NET 和 C# 设计得精妙,导致时常我们是在使用反射却感觉不到它的存在,也使得反射用起来并不复杂。
当程序处于动态期(dynamic)用户已经用上了,不再是开发时的静态期(static)。动态期用户和程序间的操作是难以预测的,如果你要在开始时将所有情况都预料到,那程序的复杂度难免太高,指不定就是成百上千的 if else,即使你真的这样做了,写出来的程序也非常难维护,可读性很低。很多时候更有可能是我们在编写程序时无法详尽枚举出用户可能进行的操作,这时我们的程序就需要一种以不变应万变的能力
注:
-
.NET Framework 和 .NET Core 都有反射机制,但它们的类库不太一样,使用反射时用到的一些 API 也略有差别,示例是基于 .NET Core 的
-
反射毕竟是动态地去内存中获取对象的描述、对象类型的描述,再用这些描述去创建新的对象,整个过程会影响程序性能,所以不要盲目过多地使用反射
依赖注入
例一:反射的原理以及和反射密切相关的一个重要技能 —— 依赖注入
例一沿用的是我上一篇文章https://blog.csdn.net/weixin_41372626/article/details/104956082 中 Tank 的例子
static void Main(string[] args)
{
// ITank、HeavyTank 这些都算静态类型
ITank tank = new HeavyTank();
// ======华丽的分割线======
// 分割线以下不再使用静态类型
var t = tank.GetType();
object o = Activator.CreateInstance(t);
MethodInfo fireMi = t.GetMethod("Fire");
MethodInfo runMi = t.GetMethod("Run");
fireMi.Invoke(o, null);
runMi.Invoke(o, null);
}
...
CreateInstance 创建出来的对象是 object 的,我们并不知道它的静态类型是什么。
public static object CreateInstance(Type type);
我们平时大多数是用封装好了的反射方法,上面这种用法很少见。
封装好了的反射最重要的一个功能就是依赖注入 DI Dependency Injection。前面学的那个依赖反转的全称是 DIP 别和 DI 搞混,但换句话说没有依赖反转也就没有依赖注入。
依赖反转是一个概念,依赖注入是在该概念之上结合接口和反射机制所形成的一个应用。
依赖注入框架
依赖注入需要借助依赖注入框架,.NET Core 的依赖注入框架就是:Microsoft.Extensions.DependencyInjection
依赖注入最重要的元素就是 Container(容器),我们把各种各样的类型和它们对应的接口都放在(注册)容器里面。回头我们创建实例时就找容器要。
容器又叫做 Service Provider
在注册类型时我们可以设置容器创建对象的规则:每次给我们一个新对象或是使用单例模式,每次要的时候容器都给我们同一个实例。
具体怎么使用容器此处按下表,主要还是看依赖注入怎么用。
static void Main(string[] args)
{
// ServiceCollection 就是容器
var sc = new ServiceCollection();
// 添加 ITank,并设置其对应的实现类是 HeavyTank
sc.AddScoped(typeof(ITank), typeof(HeavyTank));
var sp = sc.BuildServiceProvider();
// ===========分割线===========
// 分割线上面是整个程序的一次性注册,下面是具体使用
ITank tank = sp.GetService<ITank>();
tank.Fire();
tank.Run();
}
通过上面这种方法,一旦以后我的 ITank 需要改为 MediumTank, 只需要改一句话即可。
static void Main(string[] args)
{
// ServiceCollection 就是容器
var sc = new ServiceCollection();
sc.AddScoped(typeof(ITank), typeof(MediumTank));
sc.AddScoped(typeof(IVehicle), typeof(Car));
sc.AddScoped<Driver>();
var sp = sc.BuildServiceProvider();
// ===========分割线===========
var driver = sp.GetService<Driver>();
driver.Drive();
}
Driver 创建时本来需要一个 IVehicle 对象,sp 会去容器里面找,又由于我们注册了 IVehicle 的实现类是 Car,所以会自动创建一个 Car 实例传入 Driver 的构造器