反射——插件(第一方给SDK,第三方开发者基于SDK实现二次开发)
需求你是一个买婴儿车的厂家这个婴儿车有个面板显示多个小动物按下对应的小动物虚拟按键就会发出对应的小动物的叫声为了更好的销售你的需求是可以让更多的开发者开发插件来接入更多的小动物声音。一、创建主体项目主体程序下面有个Animal文件夹会把第三方小动物插件给加载进来去调用所有小动物类的Voice方法Voice方法接收一个整数表示小动物叫几次。主体程序主要利用反射来加载插件拿到这些动物类创建实例调用Voice方法。显示当前项目运行的路径Console.WriteLine(Environment.CurrentDirectory);例如我这个是 E:\Codes\My\TaiDaPLC\BabyStroller\BabyStroller.App\bin\Debug\net8.0在这个项目路径下创建一个Animals文件夹这个文件夹里面存放多个插件dll文件主体程序代码如下usingSystem;usingSystem.Runtime.Loader;namespaceBabyStroller.App{classProgram{staticvoidMain(string[]args){// Console.WriteLine(Environment.CurrentDirectory); // 当前项目所在的文件夹// E:\Codes\My\TaiDaPLC\BabyStroller\BabyStroller.App\bin\Debug\net8.0stringfolderPath.Combine(Environment.CurrentDirectory,Animals);// 当前项目所在的文件夹/Animals这个文件夹也可以直接 var folderstring[]filesDirectory.GetFiles(folder);// 拿到文件夹下面的所有文件也可以直接var filesListTypeanimalTypesnewListType();foreach(stringfileinfiles){System.Reflection.AssemblyassemblyAssemblyLoadContext.Default.LoadFromAssemblyPath(file);// using System.Runtime.Loader;命名空间下Type[]typesassembly.GetTypes();foreach(Typetintypes){if(t.GetMethod(Voice)!null)// 有规定的Voice方法{animalTypes.Add(t);// 把所有的animalType给加载到内存中}}}while(true){// 列出有多少种小动物for(inti0;ianimalTypes.Count;i){Console.WriteLine(${i1}.{animalTypes[i].Name});// 1. 小猫 2. 小狗 等形式}Console.WriteLine();// 华丽的分割线Console.WriteLine(Please choose animal:);intindexint.Parse(Console.ReadLine());// 用户输入一个整数if(index1||indexanimalTypes.Count)// 整数介于1和animalTypes.Count之间{Console.WriteLine(No such an animal. Try again!);continue;}Console.WriteLine(How many times?);inttimesint.Parse(Console.ReadLine());// 小动物叫多少次TypetanimalTypes[index-1];// 拿到小动物类型System.Reflection.MethodInfo?mt.GetMethod(Voice);// 拿到对应的小动物类型后去调用Voice方法object?oActivator.CreateInstance(t);// 用这个类型创建对象m.Invoke(o,newobject[]{times});// 调用Voice方法传入参数timesConsole.WriteLine(Done!);}}}}二、创建插件项目① 原生反射俩类库项目Animals.Lib和Animals.Lib2分别对应两个不同的插件开发者每个插件里面都有两个类类中都有对应的Voice方法Animals.Lib这个类库namespaceAnimals.Lib{publicclassCat{publicvoidVoice(inttimes){for(inti0;itimes;i){Console.WriteLine(miao miao miao~~~);}}}}namespaceAnimals.Lib{publicclassDog{publicvoidVoice(inttimes){for(inti0;itimes;i){Console.WriteLine(wang wang wang~~~);}}}}Animals.Lib2这个类库namespaceAnimals.Lib2{publicclassCow{publicvoidVoice(inttimes){for(inti0;itimes;i){Console.WriteLine(mou mou mou~~~);}}}}namespaceAnimals.Lib2{publicclassSheep{publicvoidVoice(inttimes){for(inti0;itimes;i){Console.WriteLine(mie mie mei~~~);}}}}两个类库项目生成一下得到Animals.Lib.dll和Animals.Lib2.dll对应文件夹下拿到这俩dll把这俩dll放到主体程序对应的Animals文件夹下运行主体程序这是通过原生反射去读dll里面规定的Voice函数但如果开发者Animals.Lib和Animals.Lib2在对应的函数Voice一但写错函数名称那么主体函数就读不到为了降低开发者的开发难度主体程序开发者需要提供SDK来帮助并且限制第三方开发者② SDK为了帮助第三方插件开发者避免不必要的错误并且减少其开发成本一般第一方会发布一个SDK帮助第三方开发者快速开发首先准备一个IAnimals接口接口里面就一个Voice方法所有开发动物类的开发者都要实现这个接口这样就可以保证里面有Voice方法之后创建的对象可以直接转换为接口类型的对象就可以不用弱类型的System.Reflection.MethodInfo以及Invoke等方式去调用了可以直接使用接口里面的Voice方法即可其次有时候开发者开发的小动物还没有开发完成但也放类库里面了现在可以加一个Artibuide类型把这个artibede类型给torch到小动物上然后就不load这个未开发完成的即可Ⅰ 创建主体SDK类库项目SDK是以dll的形式给开发者来使用的删掉原本的类创建一个接口接口文件名称IAnimals.csIAnimals.csnamespaceBabyStroller.SDK{publicinterfaceIAnimals{voidVoice(inttimes);}}创建一个UnfinishedAttribute类UnfinishedAttribute.csnamespaceBabyStroller.SDK{publicclassUnfinishedAttribute:Attribute//继承一下Attribute即可没有完成的功能放这里{}}生成一下项目即可拿到生成的SDKBabyStroller.SDK.dllⅡ 主体项目使用SDK添加项目引用浏览找到BabyStroller.SDK.dllⅢ 第三方开发者引用SDK第三方开发者开发的小动物类继承IAnimals接口即可因为之前就有Voice函数故不需要怎么修改若没开发完的函数可以通过Unfinished标识一下即可Animals.Lib类库项目Cat.csusingBabyStroller.SDK;namespaceAnimals.Lib{// 假如说这个Cat类没有开发完可以通过Unfinished标识一下即可[Unfinished]publicclassCat:IAnimals{publicvoidVoice(inttimes){for(inti0;itimes;i){Console.WriteLine(miao miao miao~~~);}}}}Dog.csusingBabyStroller.SDK;namespaceAnimals.Lib{publicclassDog:IAnimals{publicvoidVoice(inttimes){for(inti0;itimes;i){Console.WriteLine(wang wang wang~~~);}}}}Animals.Lib2类库项目Cow.csusingBabyStroller.SDK;namespaceAnimals.Lib2{// 假如说这个Cow类没有开发完可以通过Unfinished标识一下即可[Unfinished]publicclassCow:IAnimals{publicvoidVoice(inttimes){for(inti0;itimes;i){Console.WriteLine(mou mou mou~~~);}}}}Sheep.csusingBabyStroller.SDK;namespaceAnimals.Lib2{publicclassSheep:IAnimals{publicvoidVoice(inttimes){for(inti0;itimes;i){Console.WriteLine(mie mie mei~~~);}}}}然后分别对两个项目生成一下得到对应的新版Animals.Lib.dll和Animals.Lib2.dll放到主体项目中的Animals文件夹下Ⅳ 主体程序更新usingBabyStroller.SDK;usingSystem;usingSystem.Runtime.Loader;namespaceBabyStroller.App{classProgram{staticvoidMain(string[]args){// Console.WriteLine(Environment.CurrentDirectory); // 当前项目所在的文件夹// E:\Codes\My\TaiDaPLC\BabyStroller\BabyStroller.App\bin\Debug\net8.0stringfolderPath.Combine(Environment.CurrentDirectory,Animals);// 当前项目所在的文件夹/Animals这个文件夹也可以直接 var folderstring[]filesDirectory.GetFiles(folder);// 拿到文件夹下面的所有文件也可以直接var filesListTypeanimalTypesnewListType();foreach(stringfileinfiles){System.Reflection.AssemblyassemblyAssemblyLoadContext.Default.LoadFromAssemblyPath(file);// using System.Runtime.Loader;命名空间下Type[]typesassembly.GetTypes();// 有了SDK之后只需要判断一下这个类型是不是IAnimated接口的实现形式并且没有被unfinished所修饰// Attributed这里是Unfinished就是为了在使用反射的时候通过反射拿到一个方法或一个类看看其有没有有没有被某一个Attributed所修饰然后再选择是否调用/* foreach (Type t in types) { if (t.GetMethod(Voice) ! null) // 有规定的Voice方法 { animalTypes.Add(t); // 把所有的animalType给加载到内存中 } } */foreach(Typetintypes){if(t.GetInterfaces().Contains(typeof(IAnimals)))// 一个类型的接口里面得包含IAnimals这个类型并且这个类不能被unfinished所修饰{boolisUnfinishedt.GetCustomAttributes(false).Any(aa.GetType()typeof(UnfinishedAttribute));if(isUnfinished)continue;animalTypes.Add(t);}}}while(true){// 列出有多少种小动物for(inti0;ianimalTypes.Count;i){Console.WriteLine(${i1}.{animalTypes[i].Name});// 1. 小猫 2. 小狗 等形式}Console.WriteLine();// 华丽的分割线Console.WriteLine(Please choose animal:);intindexint.Parse(Console.ReadLine());// 用户输入一个整数if(index1||indexanimalTypes.Count)// 整数介于1和animalTypes.Count之间{Console.WriteLine(No such an animal. Try again!);continue;}Console.WriteLine(How many times?);inttimesint.Parse(Console.ReadLine());// 小动物叫多少次TypetanimalTypes[index-1];// 拿到小动物类型System.Reflection.MethodInfo?mt.GetMethod(Voice);// 拿到对应的小动物类型后去调用Voice方法object?oActivator.CreateInstance(t);// 用这个类型创建对象// 拿到对象后强制类型转换一下// m.Invoke(o, new object[] { times }); // 调用Voice方法传入参数timesvaraoasIAnimals;a.Voice(times);// 调用强类型的方法Console.WriteLine(Done!);}}}}