[我正在尝试制作一个可以动态加载和卸载售后装配并创建该装配中定义的类型的对象的应用程序,但是当该对象具有迭代器方法时,我会遇到问题。
和我在一起-最小,可复制的示例有点大,因为它包含多个部分。我将分三个阶段进行说明。
这里是基本结构无插件架构。所有这些都整合到一个组件中,只是为了说明我要使用的结构。
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);
}
}
}
然后,我将该项目分为三部分,以构建插件体系结构。
API.IHostObject
API.IPluginObject
main
ConcreteHostObject
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);
}
}
}
到目前为止一切正常。
我的印象是,只要我仅通过通用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
后缀,但我认为它与迭代器方法有关。我也浏览了these docs,特别是有关迭代器方法要求的部分,但未提及序列化要求。
有人可以解释出什么问题了,我该怎么办才能解决?
这是最小,可复制的示例。但是在我实际的插件中,GetStrings
方法实际上是一个像协程一样工作的迭代器方法,这意味着not从使用IEnumerable<string>
切换为使用string[]
是可以接受的解决方法。没有字符串的集合,也没有数组。这实际上是一个诚实善良的迭代器方法,它使用yield
并像协程一样工作。
问题是,跨越AppDomain边界的每种类型都必须可序列化。您可能会注意到,MarshalByRefObject标记有[Serializable]属性,这就是您的ConcreteHostObject能够顺利通过的原因。
但是,迭代器方法在后台做了一些编译器魔术,以使其能够正常工作,并创建(并返回)它创建的实现IEnumerable<T>
的类。 d__1后缀是一个很好的线索,它不是您自己的构造的一类。不幸的是,该类被not标记为可序列化的。如果您想要这种行为,则必须自己编写并管理自己的“收益”逻辑。