在工作学习中,我们时常明确了确定的业务流程,确定的功能提供对象,我们在确定依赖关系时,也同样是确定的。我们可以建造强类型的对象来规定某些对象或者方法的强耦合和约束。但如果现在,某些对象的创建,某些功能的选择我们只能在运行时才能得知呢?
程序处在运行时,进行时处于一个dynamic状态,而我们在编写程序时处于一个static状态,如果我们要在static状态来去预测或枚举出用户不同的状态,那这会使得程序变得非常臃肿。所以我们要使我们的程序具有以不变应万变的能力,就可以引入反射。
但谨记,反射获取的类型的信息是CLR在运行时提供的该类型对象的元数据的描述信息,所以频繁的使用反射,会影响我们的运行时性能。
先通过之前的一个例子来简单的了解一下反射的功能:反射通过元数据取得了我们某个类型的所有信息
using System; using System.Reflection; namespace LanguageLearn { public class Program { public static void Main(string[] args) { ITank tank = new SmallTank(); var o = tank.GetType(); MethodInfo methodInfo = o.GetMethod("Run"); MethodInfo methodInfo2 = o.GetMethod("Fire"); methodInfo.Invoke(tank, null); methodInfo2.Invoke(tank, null); } class Driver { private readonly IVehicle _vehicle; public Driver(IVehicle vehicle) { _vehicle = vehicle; } public void Run() { _vehicle.Run(); } } interface IVehicle { void Run(); } class Car : IVehicle { public void Run() { Console.WriteLine("car is running..."); } } class Truck : IVehicle { public void Run() { Console.WriteLine("truck is running..."); } } interface IWeapon { void Fire(); } interface ITank : IVehicle, IWeapon { } class SmallTank : ITank { public void Fire() { Console.WriteLine("small boom!!"); } public void Run() { Console.WriteLine("small tank is running..."); } } class BigTank : ITank { public void Fire() { Console.WriteLine("big boom!!"); } public void Run() { Console.WriteLine("big tank is running..."); } } } }
依赖注入又名DependencyInjection,它为了获取更松的耦合而设计的一种模式,它使得我们的功能服务,不再直接的被某个对象所依赖。而是将服务注入到运行时的服务容器中管理,而我们的接口可以注册成任意一种我们想要的实现。供程序每一处来获取使用,这个时候我们不需要创建相同类型的服务提供商了,而只需从服务容器中获取。
public class Program { public static void Main(string[] args) { var sc = new ServiceCollection(); sc.AddScoped(typeof(ITank), typeof(BigTank)); sc.AddScoped(typeof(IVehicle), typeof(Car)); sc.AddScoped<Driver>(); var sp = sc.BuildServiceProvider(); var tankServices = sp.GetService<ITank>(); tankServices.Run(); tankServices.Fire(); var driver = sp.GetService<Driver>(); driver.Run(); } class Driver { private readonly IVehicle _vehicle; public Driver(IVehicle vehicle) { _vehicle = vehicle; } public void Run() { _vehicle.Run(); } } interface IVehicle { void Run(); } class Car : IVehicle { public void Run() { Console.WriteLine("car is running..."); } } class Truck : IVehicle { public void Run() { Console.WriteLine("truck is running..."); } } interface IWeapon { void Fire(); } interface ITank : IVehicle, IWeapon { } class SmallTank : ITank { public void Fire() { Console.WriteLine("small boom!!"); } public void Run() { Console.WriteLine("small tank is running..."); } } class BigTank : ITank { public void Fire() { Console.WriteLine("big boom!!"); } public void Run() { Console.WriteLine("big tank is running..."); } } }
除了利用反射注入之外,我们还可以使用反射来做到 “以不变应万变”,现在想象我们是一个婴儿玩具一手提供商,有一个玩具根据不同的按钮发出不同动物的声音,现在有一个三方提供商,向为我们提供不同动物的叫声,目前这个方案是这样的,三方提供商提供不同“动物”的.dll程序集,而我们读取他们的程序集进行功能的接入,注意第三方提供的功能要求,需要满足我们的 “协议”, 目前我们的协议为一个方法名,和一个发出声音的次数。
三方提供商:提供了三种动物,方法大致为:
第一方提供商:
using System; using System.Collections.Generic; using System.IO; using System.Runtime.Loader; namespace BabyStroller { class Program { static void Main(string[] args) { var folder = Path.Combine(Environment.CurrentDirectory, "Animals"); var files = Directory.GetFiles(folder); var animalsTypes = new List<Type>(); foreach (var file in files) { var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file); var types = assembly.GetTypes(); foreach (var type in types) { if (type.GetMethod("Voice") != null) { animalsTypes.Add(type); } } } while (true) { for (int i = 0; i < animalsTypes.Count; i++) { Console.WriteLine($"{i + 1}.{animalsTypes[i].Name}"); } Console.WriteLine("================================"); Console.WriteLine("Please choose animal"); int index = int.Parse(Console.ReadLine()); if (index > animalsTypes.Count || index < 1) { Console.WriteLine("No Such an animal. Try again!"); continue; ; } Console.WriteLine("How many times you wanna ??"); int times = int.Parse(Console.ReadLine()); var t = animalsTypes[index - 1]; var m = t.GetMethod("Voice"); var o = Activator.CreateInstance(t); m.Invoke(o, new object[] { times }); } } } }
现在将第三方的功能文件导入到我们的程序中。
功能接入成功了
现在我们在进一步考虑,我们现在用的是纯的反射,很容易出错,比如第三方将方法名写成了小写?,所以为了避免让开发商出现这种错误,减轻他们开发的成本,一般我们会发布一个SDK来帮助第三方进行开发,我们可以在SDK中提供一个IAnimal接口,在接口中就一个Voice方法,所有开发动物类的厂商都要实现这个接口。并且如果第三方对于某个功能还没有开发完成,那么我们应该不去Load它,现在我们告诉第三方说:“如果你有一个功能没有开发完,那你记得标注一下,这样我就不加载这个待开发的功能了”。于是我们的第三方使用了Attribute...
我们新建了一个项目包含了我们以上的功能:为第三方和第一方都添加项目的引用
让第三方的功能实现都实现我们约定的接口,并新增一个类标记为未完成的类。
现在重新Build项目生成最新的程序集(版本),让第一方使用。改写我们的第一方程序代码即可。
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Loader; using BabyStroller.SDK; namespace BabyStroller { class Program { static void Main(string[] args) { var folder = Path.Combine(Environment.CurrentDirectory, "Animals"); var files = Directory.GetFiles(folder); var animalsTypes = new List<Type>(); foreach (var file in files) { var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file); var types = assembly.GetTypes(); //Search interface foreach (var type in types) { //添加实现了IAnimal接口的类 if (type.GetInterfaces().Contains(typeof(IAnimal))) { //过滤未完成的方法 var isUnfinished = type.GetCustomAttributes(false) .Any(x => x.GetType() == typeof(UnfinishedAttribute)); if (isUnfinished) { continue; } animalsTypes.Add(type); } } } while (true) { for (int i = 0; i < animalsTypes.Count; i++) { Console.WriteLine($"{i + 1}.{animalsTypes[i].Name}"); } Console.WriteLine("================================"); Console.WriteLine("Please choose animal"); int index = int.Parse(Console.ReadLine()); if (index > animalsTypes.Count || index < 1) { Console.WriteLine("No Such an animal. Try again!"); continue; ; } Console.WriteLine("How many times you wanna ??"); int times = int.Parse(Console.ReadLine()); var t = animalsTypes[index - 1]; //var m = t.GetMethod("Voice"); var o = Activator.CreateInstance(t); //m.Invoke(o, new object[] { times }); var a = o as IAnimal; a.Voice(times); } } } }