• 博客园logo
  • 会员
  • 周边
  • 众包
  • 新闻
  • 博问
  • 闪存
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
学习笔记
Misaka的学习笔记
博客园    首页    新随笔    联系   管理    订阅  订阅
c#学习笔记-------------------反射

一、什么是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()
        {
            // 空方法,因为没有非托管资源需要释放
            // 如果基类有使用非托管资源,也需处理
        }
    }

}

 

posted on 2023-07-03 00:42  我们打工人  阅读(226)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3