一、什么是C#反射(Reflection)
定义:属性提供了一种将元数据或声明性信息与代码(程序集、类型、方法、属性等)关联的强大方法。
当一个属性与一个程序实体相关联后,可以使用一种叫做 反射 .
这是.Net中获取运行时类型信息的方式,.Net的应用程序包含以下几个部分:
‘程序集(Assembly)’、
‘模块(Module)’、
‘类型(class)’,
而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息,例如:
Assembly类可以获得正在运行的装配件信息,也可以动态的加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。
Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等等,通过Type类可以得到这些要素的信息,并且调用之。
MethodInfo类包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。
诸如此类,还有FieldInfo、EventInfo等等,这些类都包含在System.Reflection命名空间下。
在讲反射的应用之前先讲清楚命名空间与装配件的关系:
命名空间:命名空间是程序设计者命名的内存区域,程序设计者根据需指定一些有名字的空间域,
把一些全局实体分别存放到各个命名空间中,从而与其他全局实体分隔开。
装配件:装配件也可以叫做程序集,是.Net应用程序执行的最小单位,编译出来的.dll、.exe都是装配件。
装配件和命名空间的关系不是一一对应,也不互相包含,一个装配件里面可以有多个命名空间,一个命名空间也可以在多个装配件中存在
类比成民族就好理解多了,比如有人是汉族、有人是回族,有人住在北京、有人住在上海;那么北京有汉族人,也有回族人,上海有汉族人,也有回族人,这是不矛盾的。
二、反射的使用,反射有什么用
编译中分为动态编译和静态编译,静态编译是在编译中确定类型,绑定对象,而动态编译是在运行中确定类型,绑定对象
反射就是一种可以动态创建对象、绑定对象的手段。但是缺点是性能上不如静态绑定
既然在开发时就能够写好代码,干嘛还放到运行期去做,其实不然,这种操作提高了程序的灵活性和扩展性
比如很多软件开发者喜欢在自己的软件中留下一些接口,其他人可以编写一些插件来扩充软件的功能,
比如我有一个媒体播放器,我希望以后可以很方便的扩展识别的格式,那么我声明一个接口:
public interface IMediaFormat { string Extension {get;} Decoder GetDecoder(); }
这个接口中包含一个Extension属性,这个属性返回支持的扩展名,另一个方法返回一个解码器的对象
(这里我假设了一个Decoder的类,这个类提供把文件流解码的功能,扩展插件可以派生之),
通过解码器对象我就可以解释文件流。那么我规定所有的解码插件都必须派生一个解码器,并且实现这个接口,
在GetDecoder方法中返回解码器对象,并且将其类型的名称配置到我的配置文件里面。
这样的话,我就不需要在开发播放器的时侯知道将来扩展的格式的类型,只需要从配置文件中获取现在所有解码器的类型名称,而动态的创建媒体格式的对象,将其转换为IMediaFormat接口来使用。
如何通过反射获取类型,以下是示例:
static void Main(string[] args) { //通过对象获取到这个对象所属类的Type对象 TestClass c=new TestClass (); //Type t = c.GetType (); //通过Type类中的静态方法GetType获取到类的Type对象 //Type t = Type.GetType("TestClass"); //3.通过typeof关键字获取到类的Type对象 Type t = typeof(TestClass); Console.WriteLine(t.Name);//获取类名(不带命名空间) Console.WriteLine(t.FullName);//获取类名(带命名空间) Console.WriteLine(t.Assembly);//获取程序集 Console.WriteLine(t.BaseType);//获取基类类型 Console.WriteLine("----------获取类中字段"); var fileds = t.GetFields(); foreach (var f in fileds) { Console.WriteLine(f.Name); } Console.WriteLine("----------获取类中属性"); var properties = t.GetProperties(); foreach (var p in properties) { Console.WriteLine(p.Name); } Console.WriteLine("----------获取类中方法"); var methods = t.GetMethods(); foreach (var m in methods) { Console.WriteLine(m.Name); } Console.WriteLine("----------获取类中成员"); var members = t.GetMembers(); foreach (var m in members) { Console.WriteLine(m.Name); } Console.WriteLine("----------获取类中嵌套类"); var nesteds = t.GetNestedTypes(); foreach (var nested in nesteds) { Console.WriteLine(nested.Name); } Console.WriteLine("----------获取类中构造函数"); var constructors = t.GetConstructors(); foreach (var constructor in constructors) { Console.WriteLine(constructor.Name); } Console.WriteLine("----------获取类中事件"); var events = t.GetEvents(); foreach(var item in events) { Console.WriteLine(item.Name); } Console.WriteLine("----------获取类继承的接口"); var faces = t.GetInterfaces(); foreach (var item in faces) { Console.WriteLine(item.Name); } Console.WriteLine("----------获取类继承的父类"); var father = t.BaseType; Console.WriteLine(father.Name); //获取所有程序集 var allAssemblies = AppDomain.CurrentDomain.GetAssemblies(); Console.ReadKey(); } public interface TestFace { string Name { get; } Decoder GetDecoder(); } public class TestFatherClass { } public class TestClass : TestFatherClass,TestFace { public delegate void Foo(); public event Foo OnFoo; public string str; public int num { get; set; } public string Name => throw new NotImplementedException(); public void Fun() { } public Decoder GetDecoder() { throw new NotImplementedException(); } public class TestNestedClass { } }
接下来就是根据动态获取的类创建对于的实例了:
static void Main(string[] args) { //通过对象获取到这个对象所属类的Type对象 //TestClass c=new TestClass (); //Type t = c.GetType (); //通过Type类中的静态方法GetType获取到类的Type对象 //Type t = Type.GetType("TestClass"); //3.通过typeof关键字获取到类的Type对象 Type t = typeof(TestClass); //System.Activator提供了方法来根据类型动态创建对象 object c = Activator.CreateInstance(typeof(TestClass), "hello world"); //调用之前需要转换一下类型 Console.WriteLine(((TestClass)c).str); //动态调用方法 ((TestClass)c).Fun(); //调用类中的委托 // 获取类型,实际上这里也可以直接用typeof来获取类型 TestClass obj = new TestClass(""); Type type = Type.GetType("ConsoleApp1.TestClass"); TestDelegate method = (TestDelegate)Delegate.CreateDelegate(type, obj, "GetValue"); String returnValue = method("hello"); Console.WriteLine(returnValue); Console.ReadKey(); } public interface TestFace { string Name { get; } Decoder GetDecoder(); } public class TestFatherClass { } delegate string TestDelegate(string value); public class TestClass : TestFatherClass,TestFace { public string GetValue(string value) { return value; } public delegate void Foo(string value); public event Foo OnFoo; public string str; public TestClass(string str) { this.str= str; } public int num { get; set; } public string Name => throw new NotImplementedException(); public void Fun() { Console.WriteLine("我被调用啦!"); } public Decoder GetDecoder() { throw new NotImplementedException(); } public class TestNestedClass { } }
当然也有标准的写法:
//通过构造函数创建实例 Type t = typeof(TestClass); Type[] paramTypes = new Type[1] { typeof(string) }; var info = t.GetConstructor(paramTypes); object[] param = new object[1] { "hello world" }; var o = info.Invoke(param); Console.WriteLine(((TestClass)o).str);
//反射赋值 var data = Activator.CreateInstance(Type.GetType("TestFatherClass")); var finfo = data.GetType().GetField("str"); finfo.SetValue(data, "Hello World!!"); Console.WriteLine(((TestFatherClass)data).str); public class TestFatherClass { public string str; }
//System.Activator提供了方法来根据类型动态创建对象 object c = Activator.CreateInstance(typeof(TestClass), "hello world"); //调用之前需要转换一下类型 Console.WriteLine(((TestClass)c).str); //动态调用方法 var m1 = c.GetType().GetMethod("Fun"); m1.Invoke(data, new object[] { "参数" });
三、反射实例:使用接口和反射制作热插拔动态加载类库
预计实现效果:
启动程序后打开插件目录,将准备好的插件dll复制到插件目录下,程序可动态加载该dll的功能
可以看到,当这个目录下面啥都没有时程序没有任何功能
当把准备好的插件放入指定目录下,程序就会出现插件的功能
代码实现如下:
插件管理对象:
using System.Reflection; using System.Runtime.Loader; using System.Text; namespace PluginBase { public class PluginManager { private readonly string _pluginPath; private FileSystemWatcher? _watcher; private readonly List<IPlugin> _plugins = new(); public List<IPlugin> Plugins { get => _plugins; } public event Action? PluginsUpdated; public PluginManager(string pluginPath) { //设置插件文件夹 _pluginPath = pluginPath; //开启文件夹监控 StartWatching(); } ~PluginManager() { //停止监控文件夹 StopWatching(); } /// <summary> /// 加载文件夹所有插件 /// </summary> public void LoadPlugins() { //判断文件夹是否存在 if (!Directory.Exists(_pluginPath)) { return; } //清空List集合 _plugins.Clear(); Array.ForEach(Directory.GetFiles(_pluginPath, "*.dll", SearchOption.AllDirectories), file => LoadPlugin(file)); } /// <summary> /// 加载单个插件 /// </summary> /// <param name="pluginPath"></param> public void LoadPlugin(string pluginPath) { //程序集对象 Assembly pluginAssembly; try { //根据地址加载程序集 pluginAssembly = Assembly.LoadFrom(pluginPath); } catch (Exception ex) { LogExceptionDetails(ex, $"加载插件assembley失败,路径:{pluginPath}"); return; } // 获取所有实现了IPlugin接口的类 var pluginTypes = pluginAssembly.GetTypes() .Where(type => typeof(IPlugin).IsAssignableFrom(type)); ////下面代码更宽泛,继承来的实现也算,上面的不算 //var pluginTypes = pluginAssembly.GetTypes() // .Where(type => type.GetInterfaces().Contains(typeof(IPlugin))); //判断是否获取成功 if (pluginTypes is null) return; //循环实现IPlugin接口的类 foreach (var pluginType in pluginTypes) { try { // 创建插件实例 var plugin = Activator.CreateInstance(pluginType) as IPlugin; // 执行插件特定初始化操作 plugin?.Load(); // 添加到插件列表 if (plugin != null) _plugins.Add(plugin); } catch (Exception ex) { LogExceptionDetails(ex, $"插件创建失败!"); } } } public void UnloadPlugins() { StopWatching(); _plugins.ForEach(plugin => plugin.Dispose()); _plugins.Clear(); PluginsUpdated?.Invoke(); } public void UnloadPlugin(string pluginPath) { Assembly? pluginAssembly; try { //使用Location获取dll地址,与参数比较得到内存中对应程序集 pluginAssembly = AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(asm => asm.Location == pluginPath); } catch (Exception ex) { LogExceptionDetails(ex, $"卸载插件assembley失败,路径:{pluginPath}"); return; } // 获取所有实现了IPlugin接口的类 var pluginTypes = pluginAssembly?.GetTypes() .Where(type => typeof(IPlugin).IsAssignableFrom(type)); if (pluginTypes is null) return; foreach (Type pluginType in pluginTypes) { foreach (var plugin in _plugins) { if (plugin.GetType() == pluginType) plugin.Dispose(); } _plugins.RemoveAll(r => r.GetType() == pluginType); } } /// <summary> /// 监听目录变化 /// </summary> private void StartWatching() { if (!Directory.Exists(_pluginPath)) { return; } _watcher = new FileSystemWatcher(_pluginPath, "*.dll"); _watcher.IncludeSubdirectories = true; _watcher.Created += Watcher_Created; _watcher.Deleted += Watcher_Deleted; _watcher.EnableRaisingEvents = true; } /// <summary> /// 停止目录监控 /// </summary> private void StopWatching() { if (_watcher != null) { _watcher.Created -= Watcher_Created; _watcher.Deleted -= Watcher_Deleted; _watcher.Dispose(); _watcher = null; } } private void Watcher_Created(object sender, FileSystemEventArgs e) { LoadPlugin(e.FullPath); PluginsUpdated?.Invoke(); } private void Watcher_Deleted(object sender, FileSystemEventArgs e) { UnloadPlugin(e.FullPath); PluginsUpdated?.Invoke(); } private void LogExceptionDetails(Exception ex, string message) { StringBuilder sb = new StringBuilder(); sb.AppendLine($"记录时间:{DateTime.Now:g}"); sb.AppendLine($"错误信息:{message}"); sb.AppendLine(ex.ToString()); sb.AppendLine(ex.StackTrace); File.AppendAllText("ExceptionDetails.log", sb.ToString()); } public void ExecuteFunc((Guid,string,string) cmd) { var Item = Plugins.Where(r => r.Guid == cmd.Item1 && r.Menu == cmd.Item2 && r.Name == cmd.Item3 ).FirstOrDefault(); Item?.Execute(); } } }
对应程序集开放的接口:
namespace PluginBase { public interface IPlugin:IDisposable { Guid Guid { get; } string Menu { get; } string Name { get; } void Execute(); void Load(); } } #region 为什么要有Load方法 //将一部分初始化逻辑放到 Load 方法中, //可以帮助保持构造方法的简洁性、 //提供更灵活的初始化方式(可选择是否Load), //并允许处理异步操作或按需加载的场景。 #endregion #region 为什么要有Dispose方法 //非托管资源(如文件句柄、数据库连接、网络连接等)需要手动释放 //关闭文件、取消订阅事件、释放定时器等。Dispose 方法提供了统一的位置 //实现 IDisposable 接口, 可利用 using 语句或手动调用 Dispose 方法 #endregion
调用方法:
private void UpdatePluginList() { try { if (this.InvokeRequired) { this.Invoke(UpdatePluginList); return; } pluginsMenu.DropDownItems.Clear(); // 存储每个菜单项所对应的Guid Dictionary<string, Guid> menuGuids = new(); foreach (var plugin in _pluginManager.Plugins) { if (!string.IsNullOrEmpty(plugin.Menu) && !string.IsNullOrEmpty(plugin.Name) && plugin.Guid != default(Guid)) { var menuItemName = plugin.Menu; if (menuGuids.ContainsKey(menuItemName) && menuGuids[menuItemName] == plugin.Guid) { var existingMenuItem = pluginsMenu.DropDownItems .OfType<ToolStripMenuItem>() .FirstOrDefault(menuItem => menuItem.Text.Equals(menuItemName)); if (existingMenuItem != null) { existingMenuItem.DropDownItems.Add(CreatePluginMenuItem(plugin)); } } else { // 创建一个新的菜单项并添加到Plugins菜单中 var newFirstLevelMenuItem = new ToolStripMenuItem(plugin.Menu); var newSecondLevelMenuItem = CreatePluginMenuItem(plugin); newFirstLevelMenuItem.DropDownItems.Add(newSecondLevelMenuItem); pluginsMenu.DropDownItems.Add(newFirstLevelMenuItem); // 保存菜单项名称和ID的映射关系 menuGuids[menuItemName] = plugin.Guid; } } } } catch (InvalidOperationException) { UpdatePluginList(); //如果期间集合变化, 重新刷新显示 return; } catch { // 异常处理逻辑 } } event Action<(Guid, string, string)> CmdClick; private ToolStripMenuItem CreatePluginMenuItem(IPlugin plugin) { var menuItem = new ToolStripMenuItem(plugin.Name); menuItem.Click += (sender, e) => { CmdClick?.Invoke((plugin.Guid, plugin.Menu, plugin.Name)); }; return menuItem; }
对应的程序集:
using PluginBase; namespace Test { public class Test : IPlugin { public Guid Guid => new Guid("C2A62F4E-44E8-4349-A0CE-BF562441BC04"); public string Menu => "测试"; public string Name => "Test"; public void Execute() { MessageBox.Show("插件方法执行了","友情提示"); } public void Load() { //为每个插件提供自定义初始化方法。 //有些插件需要创建实例后初始化方法才能正常工作 } public void Dispose() { // 空方法,因为没有非托管资源需要释放 // 如果基类有使用非托管资源,也需处理 } } }