1

我正在尝试制作一个可以动态加载和卸载售后程序集并创建该程序集中定义的类型的对象的应用程序,但是当对象具有迭代器方法时我遇到了问题。

和我在一起——最小的、可重现的示例有点大,因为它有多个部分。我将分三个阶段进行解释。

阶段1

这是没有插件架构的基本结构。在这里将所有这些都集中到一个组件中只是为了说明我要使用的结构。

using API;

namespace API
{
    public interface IHostObject
    {
        string Name { get; set; }
    }

    public interface IPluginObject
    {
        void DoSomething(API.IHostObject hostObject);
    }
}


namespace Plugin
{
    class ConcretePluginObject : API.IPluginObject
    {
        void IPluginObject.DoSomething(IHostObject hostObject)
        {
            System.Console.WriteLine(hostObject.Name);
        }
    }
}

namespace Host
{
    class ConcreteHostObject : API.IHostObject
    {
        public string Name { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            IHostObject hostObject = new ConcreteHostObject() { Name = "Hosty McHostface" };
            IPluginObject pluginObject = new Plugin.ConcretePluginObject();

            pluginObject.DoSomething(hostObject);
        }
    }
}

阶段2

然后我把这个项目分成三个部分来制作插件架构。

API.dll

  • API.IHostObject
  • API.IPluginObject

主机.exe

  • main
  • ConcreteHostObject

插件.dll

  • ConcretePluginObject

我有一些执行此操作的激活码:

using System;
using API;

namespace Host
{
    class ConcreteHostObject : MarshalByRefObject, API.IHostObject
    {
        public string Name { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var appDir = AppDomain.CurrentDomain.BaseDirectory;
            var pluginsDir = System.IO.Path.Combine(appDir, "Plugins");
            var appDomainSetup = new AppDomainSetup {
                ApplicationName = "",
                ShadowCopyDirectories = "true",
                ApplicationBase = pluginsDir,
                CachePath = "VSSCache"
            };
            AppDomain apd = AppDomain.CreateDomain("NewZealand", null, appDomainSetup);

            API.IPluginObject pluginObject = (API.IPluginObject)apd.CreateInstance("Plugin", "Plugin.ConcretePluginObject").Unwrap();

            IHostObject hostObject = new ConcreteHostObject() { Name = "Hosty McHostface" };
            pluginObject.DoSomething(hostObject);
        }
    }
}

到目前为止,这一切都很好。

第 3 阶段 - 这是问题所在

我的印象是,只要我只通过在通用 API 程序集中定义的接口访问对象,一切都会好起来的。但是现在当我将一个IEnumerable<string>函数添加到我的IPluginObject.

    public interface IPluginObject
    {
        void DoSomething(API.IHostObject hostObject);
        IEnumerable<string> GetStrings(); // Added this
    }

它是这样实现的:

using System;
using System.Collections.Generic;
using API;

namespace Plugin
{
    class ConcretePluginObject : MarshalByRefObject, API.IPluginObject
    {
        void IPluginObject.DoSomething(IHostObject hostObject)
        {
            System.Console.WriteLine(hostObject.Name);
        }

        public IEnumerable<string> GetStrings() // Added this iterator method
        {
            yield return "one";
            yield return "two";
            yield return "three";
        }
    }
}

现在当我打电话时pluginObject.GetStrings(),我得到一个例外:

System.Runtime.Serialization.SerializationException
  HResult=0x8013150C
  Message=Type 'Plugin.ConcretePluginObject+<GetStrings>d__1' in Assembly 'Plugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.
  Source=mscorlib
  StackTrace:
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at API.IPluginObject.GetStrings()
   at Host.Program.Main(String[] args) in E:\Dev\Test\PluginTest\Host\Program.cs:line 27

我认为这会起作用,但似乎有一些关于迭代器(返回 IEnumerable 并使用yield关键字来执行此操作的函数)使其停止工作。

这里发生了什么?

我承认我无法理解d__1类型名称中的后缀,Plugin.ConcretePluginObject+<GetStrings>d__1但我认为它与迭代器方法有关。我还浏览了这些文档,尤其是关于迭代器方法要求的部分,但它没有说明序列化要求。

有人可以解释出了什么问题以及我能做些什么来解决它吗?

重要要求

这是一个最小的、可重现的示例。但在我的实际插件中,该GetStrings方法实际上是一个迭代器方法,它像协程一样工作,这意味着从 using 切换到using不是可接受的解决方法。没有字符串的集合,也没有数组。这确实是一个诚实的迭代器方法,它利用协程并像协程一样工作。IEnumerable<string>string[]yield

4

1 回答 1

1

问题是每个跨越 AppDomain 边界的类型都必须是可序列化的。您可能会注意到 MarshalByRefObject 标有 [Serializable] 属性,这就是您的 ConcreteHostObject 能够很好地跨越的原因。

然而,迭代器方法在幕后做了一些编译器魔法,以使其正常工作并创建(并返回)它定义的实现IEnumerable<T>. d__1 后缀是一个很好的线索,表明这不是您自己构造的类。不幸的是,这个类没有被标记为可序列化的。如果你想要这种行为,你必须自己编写它并管理你自己的“屈服”逻辑。

于 2020-03-02T21:52:01.690 回答