I'm trying to make an application that can dynamically load and unload an after-market assembly and create an object of a type defined in that assembly, but I'm running into problems when the object has an iterator method.
Stay with me -- the Minimal, Reproducible Example is a little bit big because it has multiple parts. I'll explain it in three phases.
Here's the basic structure without the plugin architecture in place. This is all glommed into one assembly here only to illustrate the structure I'm going for.
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);
}
}
}
Then I split this project into three pieces to make the plugin architecture.
API.IHostObject
API.IPluginObject
main
ConcreteHostObject
ConcretePluginObject
I have some activation code that does this:
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);
}
}
}
This all works great so far.
I was under the impression that as long as I only access the objects via interfaces that are defined in a common API assembly that everything would be fine. But now I'm getting into trouble when I add an IEnumerable<string>
function into my IPluginObject
.
public interface IPluginObject
{
void DoSomething(API.IHostObject hostObject);
IEnumerable<string> GetStrings(); // Added this
}
And it's implemented like 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";
}
}
}
Now when I call pluginObject.GetStrings()
, I get an exception:
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
I thought this was going to work, but there seems to be something about the iterator (the function that returns IEnumerable and makes use of the yield
keyword to do so) that makes it stop working.
What's going on here?
I admit I can't make sense of the d__1
suffix in the type name Plugin.ConcretePluginObject+<GetStrings>d__1
but I'm thinking it has something to do with the iterator method. I also had a look through these docs, especially the part about the requirements for iterator methods, but it doesn't say anything about serialization requirements.
Can someone please explain what went wrong and what I can do to fix it?
This is a Minimal, Reproducible Example. But in my actual plugin, the GetStrings
method is actually an iterator method that works like a coroutine, meaning it is not an acceptable workaround to switch from using IEnumerable<string>
to using string[]
. There is no collection of strings, and there is no array. This is really an honest-to-goodness iterator method that makes use of yield
and works like a coroutine.
The problem is that every type that crosses the AppDomain boundary must be serializable. You may notice that MarshalByRefObject is marked with the [Serializable] attribute, which is why your ConcreteHostObject was able to cross over just fine.
An iterator method, however, does some compiler magic under the covers to allow it to work correctly and creates (and returns) a class it defined that implements IEnumerable<T>
. The d__1 suffix is a good clue that this isn't a class of your own construction. Unfortunately, this class is not marked as serializable. If you want that behavior, you'll have to write it yourself and manage your own 'yield' logic.
User contributions licensed under CC BY-SA 3.0