MarshalByRefObject с методом итератора (IEnumerable <T>) не работает - PullRequest
1 голос
/ 03 марта 2020

Я пытаюсь создать приложение, которое может динамически загружать и выгружать сборку стороннего производителя и создавать объект типа, определенного в этой сборке, но у меня возникают проблемы, когда у объекта есть метод итератора.

Оставайтесь со мной - минимальный воспроизводимый пример немного большой, потому что состоит из нескольких частей. Я объясню это в три этапа.

Фаза 1

Вот базовая c структура без имеющейся архитектуры плагина. Все это объединено в одну сборку, чтобы проиллюстрировать структуру, к которой я иду.

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

Host.exe

  • main
  • ConcreteHostObject

Plugin.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 в типе name Plugin.ConcretePluginObject+<GetStrings>d__1 но я думаю, что это как-то связано с методом итератора. Я также просмотрел эти документы , особенно часть, касающуюся требований к методам итераторов, но ничего не говорится о требованиях к сериализации.

Может кто-нибудь объяснить, что пошло не так и что я могу сделать, чтобы это исправить?

Важное требование

Это Минимальный воспроизводимый пример . Но в моем настоящем плагине метод GetStrings на самом деле является методом итератора, который работает как сопрограмма, то есть не приемлемый обходной путь для перехода от использования IEnumerable<string> к использованию string[]. Здесь нет коллекции строк и нет массива. Это действительно честный итерационный метод, который использует yield и работает как сопрограмма.

1 Ответ

0 голосов
/ 03 марта 2020

Проблема в том, что каждый тип, который пересекает границу AppDomain, должен быть сериализуемым. Вы можете заметить, что MarshalByRefObject помечен атрибутом [Serializable], именно поэтому ваш ConcreteHostObject смог очень просто перейти.

Метод итератора, однако, делает некоторые компиляторы волхвами c под прикрытием, чтобы позволить ему работать правильно, и создает (и возвращает) определенный им класс, который реализует IEnumerable<T>. Суффикс d__1 является хорошим признаком того, что это не класс вашей собственной конструкции. К сожалению, этот класс не помечен как сериализуемый. Если вам нужно такое поведение, вам придется написать его самостоятельно и управлять своей собственной логикой «yield» c.

...