C# 使对象保持活动状态的插件系统

C# 使对象保持活动状态的插件系统,c#,dll,plugins,.net-assembly,appdomain,C#,Dll,Plugins,.net Assembly,Appdomain,我有一个应用程序,需要在运行时以.dll文件的形式加载和卸载一些插件。这些dll文件包含一个或多个派生自我的抽象类模块的类,如下所示: public abstract class Module : MarshalByRefObject { //various fields here, not reported as they are not useful to understand public abstract void Print(); } 据我所知:在C#中卸载程序集及

我有一个应用程序,需要在运行时以.dll文件的形式加载和卸载一些插件。这些dll文件包含一个或多个派生自我的抽象类模块的类,如下所示:

public abstract class Module : MarshalByRefObject
{
    //various fields here, not reported as they are not useful to understand

   public abstract void Print();

}
据我所知:在C#中卸载程序集及其类的唯一方法是将程序集放在专用的Appdomain中,然后在不再需要时卸载它,Appdomain之间通信的唯一方法是从MarshalByRefObject或MarshalByValueObject派生。然后,当我将模块的引用传递给另一个AppDomain时,我并没有真正得到对象,而是得到一个透明的代理。有人能确认我上面写的吗

现在真正的问题是:假设在我的文件PersonPlugin.dll中有一个扩展模块的“Person”类。此类包含字段名称、姓氏和电话号码

方法Print会定期调用(定期调用Print没有意义,但这是一个示例)打印这3个字段

后来,我用新版本的类Person构建了一个新的dll,它有一个名为address的新字段,Print method现在也打印地址。 注:插件是由第三方制作的,我不知道新版本是否与旧版本相似或完全不同,但在我的情况下,可以安全地假设一个职业人士在不同版本之间不会有太大差异

然后,我用这个新文件替换旧的dll文件(dll在专用的appdomain中被影子复制,因此该文件是可写/可删除的)

FileSystemWatcher会注意到更改,并:

  • 为新dll创建新的appdomain

  • 保存旧实例化的Person对象的状态

  • 将Person对象从旧Person类指定给新Person类

  • 卸载包含old Person类的appdomain

  • 应用程序开始打印还包含地址的新字符串(至少在开头为空)

  • 第2点和第3点是我问题的核心:在卸载这些对象的类之后,如何保持这些对象(或至少是它们的字段)的活动性,然后实例化一个具有相同名称和类似属性的新类,但实际上这是另一个类

    到目前为止,我的想法是:

  • 使用反射,我可以在字典中复制每个Person对象的每个字段,删除旧对象,然后实例化新对象,并在可能的情况下复制回字段(如果不存在旧字段,则跳过它;如果存在新字段,则获取其默认值)

  • 使用MessagePack之类的工具自动序列化旧的Person对象,然后将它们反序列化为新的Person对象

  • 然而,我没有做任何测试来看看这两个解决方案是否可行,主要是因为在开始编写代码之前,我想知道这两个解决方案在现实中是否可行,或者只是在我的脑海中,因为也许你们中的一些人有一个更完善的/可行的解决方案,或者甚至更好的框架/库已经做到了这一点

    更新:

    好的,我意识到在没有背景的情况下问这个问题可能会导致一些误解,所以: 我要问的问题是关于我的论文,这是一个模块化的最小游戏引擎(不是服务器)。其想法是使引擎模块化,并且为了使测试各种功能变得简单,可以在运行时立即更改其模块,而无需重新启动或失去游戏中“参与者”的状态。 dll包含1到多个模块。每个dll都加载在1个AppDomain中,因此如果卸载该AppDomain,则卸载其中的每个模块。同一appdomain中的模块可以相互直接引用,因为卸载时它们会一起卸载。不同程序集中的模块(也包括不同的AppDomain)使用消息总线进行通信,并且从来没有直接引用。如果卸载了使用该消息的模块,系统将停止向该模块转发该消息

    重要的是,模块所代表的内容取决于引擎的用户:模块可以代表单个游戏对象,如汽车,或整个物理模块。我不想深入了解更多细节,因为它们目前毫无用处,但我想实现的目标可以通过以下例子来说明:

    我有一个dll,其中包含一个名为Car的模块,它总是向前移动。我用一个包含Car模块的新dll更改dll,该模块现在总是向后移动。 结果是,一旦我更换dll,汽车将立即反转其方向

    当然,这个例子很愚蠢,有一些方法可以用一种更简单的方式来实现。然而,这也适用于我在代码中发现错误,我纠正它,提交它,然后错误消失的情况,或者更复杂的情况

    这就是为什么我需要让对象保持活动状态(我的意思是,让它们的状态保持活动状态)以及整个系统


    关于你的第2点:没有硬性限制,为什么我不允许两个相同类型但不同版本的模块共存,只要它们在不同的名称空间或不同的DLL中,因为在我的系统中,每个模块都用不同的ID标识和引用

    我想你真的想玩大黄蜂的巢

    有一些设计注意事项您没有提到

    1) 为什么你需要一直在线。为什么你不能离线,比如说每周重启一次1小时。无论您实现什么解决方案(反射/序列化),都会给您带来性能成本,更不用说维护方面的巨大开销了

    2) 有没有可能你想要相同插件的两个版本。在java世界中,有一个称为OSGI的框架。你要做的是依赖于某个模块,但在某些情况下,同一模块的两个版本可能同时在线。尤其地
    public interface IModule
    {
        Person GetPerson();
        void Print();
    }
    
    public class Person : MarshalByRefObject
    {
        ...
    }
    
    public interface IModule
    {
        Person GetPerson();
        void Print();
    
        IDictionary<string, object> GetState();
        void SetState(IDictionary<string, object> state);
    }
    
    public class ModuleManager : MarshalByRefObject
    {
        public IModule Module { get; set; }
    
        public void Initialize(string path)
        {
            // Load the module
            // Setup file watcher
        }
    
        public void SaveState(string file)
        {
            var state = this.Module.GetState();
            // Write this state to a file
        }
    
        public void LoadState(string file)
        {
            var state = this.ReadState(file);
            this.Module.SetState(state);
        }
    
        private IDictionary<string, object> ReadState(string file)
        {
            ...
        }
    }