C# | 基础,常看常新(二)

175 阅读6分钟

 

 六、反射、特性、依赖注入

—— 反射大多数时候和接口、依赖反转原则配合使用 —— 因此本章需要配合前文<接口>来理解。

—— 特性和依赖注入都是基于.Net的反射机制,三者关系密切。

6.1 关于反射

反射,Reflect,不是C#语言的功能,是.NET框架提供的功能。

它的地位非常重要,是java、C#等托管类型语言 和 C、C++等原生语言相比,最大的区别之一。

反射应用的初衷:程序的逻辑充满不确定(很多时候要等到用户交互时才能确定),如果想要在静态编译状态下预测出各种结果,就需要大量的逻辑判断,程序会变得臃肿。而且更重要的一点是:往往很难穷尽各种可能 —— 这时就需要程序有一个“以不变应万变”的方法。

反射可以实现:给它一个对象,它可以在不用new操作符、也不知道对象具体是什么静态类型的情况下,创建出一个同类型的对象 —— 进一步解耦

因此,在我们实际编程过程中,很少直接接触反射,大部分时候使用的都是封装好的反射。(单元测试、依赖注入、泛型编程都是基于反射机制。

 

6.2 应用案例1 : 直接使用反射

注意:

  • .NET Framework & NET Core都有反射机制,但是二者调用的类库不一样。(下面用.NET Core来做案例)
  • 程序中不要盲目使用反射机制,以免影响程序性能。

 

6.3 应用案例2 :通过依赖注入DI使用反射

DI和DIP的关系:DIP是个设计概念,DI是在这个概念的基础上,结合接口、反射机制所形成的应用。没有DIP就没有DI。

依赖注入要借助依赖注入框架:

 代码中引入命名空间:

使用之前需要了解——

依赖注入中最重要的一个概念:container。也称ServiceProvider / ServiceCollection

原理是:把各种类型和对应的接口放入容器,之后就可以直接向容器请求对象(可以设置请求对象的模式:瞬态、范围、单例)

 

(1)依赖注入的基本用法

结合以上代码可知,使用 依赖注入的好处

假如业务需求更新,原本使用的HeavyTank类需要变更为MiddleTank。

如果是按照传统的new方式实例化对象,那程序中所有new的地方都需要修改。有多少个new就要修改多少次。

而使用依赖注入的方式,只需要修改一行代码 —— 变更放入容器中的类型即可:

sc.AddScoped(typeof(ITank), typeof(LightTank));

 

(2)进阶用法

**如以上代码,当想要创建一个Driver实例——

传统流程:首先创建一个驾驶的IVehicle类型的对象Car,将对象传给Driver的构造器……

依赖注入流程:直接创建Driver实例。这时系统会自动去找一个IVehicle类型的实例(因为driver和Ivehicle都放置到容器中了),容器里IVehicle注册的对象是Car,所以系统会创建一个Car的对象给Driver—— 这部分的工作都由系统来完成。实际写的代码只要一行即可。

Driver driver = sp.GetService<Driver>();

同理,如果需要把Car改为其他类,也只需要修改一行代码即可:

sc.AddScoped(typeof(IVehicle), typeof(Bicycle));

注入体现在:用注册好的类型创建的实例注入到Driver的构造器。

 

 

6.4 应用案例3:反射的另一用途——松耦合

反射的第一个用途:依赖注入

反射的第二个用途:松耦合

松耦合主要体现在插件式编程。

插件式编程:不与主题程序一起编译,但可以与主体程序一起工作的组件。往往由第三方提供。

插件式编程的好处:以主题程序为中心,生成生态圈。主体——》插件

—— 主体和插件的关系,就是反射的以不变应万变的关系。

一般情况下,主题程序都包含有api,sdk,第三方可以借助这些来更便捷、更规范的进行开发,方便与主程序对接。

 

案例:

假如我是一个婴儿车开发厂商,新开发了一款支持识别动物功能的婴儿车——

婴儿点击数字面板上的小动物头像,再点击开发次数,音响就会发出小动物的叫声。

婴儿车出厂时只默认提供几种小动物的配置;为了提高产品竞争力,我在车上留了一个USB插口,允许其他厂商通过U盘来实现动物种类的扩充。

  • 这里的主体程序:婴儿车识别动物功能
  • 组件程序:动物类库插件

主体程序根目录下存在Animals文件夹,存放其他厂商开发的小动物插件。

主题程序会读取文件夹,加载第三方插件,调用第三方所有小动物类的Voice方法。

USB接口规定:插件中的小动物类要包含一个Voice方法,且要可以接受一个int类型的参数,用来判断小动物叫几次。

 

(1)纯反射模式开发

主体程序:ReflectDemoApp1

 

组件程序:Animals.Lib & Animals.Lib2

组件程序完成后,编译,到根目录复制两个 dll文件,放入主体程序的Animals文件夹 —— 即完成USB插U盘的步骤

之后正常运行主体程序—— 

运行结果:

到这里我们可以发现,使用纯反射有一些缺点:

比如插件开发商写错了动物类中的方法名——主体调用时会直接过滤掉这个类。

再比如,调用……

——为了避免如上这些不必要的错误、降低开发成本,第一方往往会发布SDK,帮助第三方快速开发。

 

(2)SDK模式开发

SDK需要考虑到的功能:

  • 统一规范:准备一个IAnimal接口,封装Voice方法,所有开发动物类的第三方都要实现这个接口——主体程序使用时,可以直接将对象转化为IAnimal类型的对象,就不需再用弱类型(method.Invoke)方式来调用Voice方法。
  • 考虑到第三方的组件中有时可能包含未开发完成的动物类,主体需要加一个artibute类型,第三方把attribute attach到未开发完的动物类,主体会自动过滤调这部分

SDK:

SDK在主体外,是一个类库,以dll的形式给到第三方。第三方开发插件时引用dll即可。

 PS.主体程序也需要包含此SDK

组件更新:

 组件改动后,要把新版本的组件 dll 加入到主体程序

主体程序更新:

运行结果:

 

ps.案例源码:

 

 

七、泛型、partial类、枚举、结构体

7.1 泛型