2015-10-26 3 views
24

Просто для уточнения, у меня это работает с использованием динамического и MakeGenericType. Но я не могу помочь, но думаю, что есть лучший способ сделать это. То, что я пытаюсь сделать, это создать «подключаемый» загрузчик, используя Unity. Я просто объясню это, когда отправлю код, чтобы вы могли понять, что я делаю.Возвращение экземпляра родового типа к функции, разрешенной во время выполнения

Сначала я просто опубликовать плагин в себя:

[RegisterAction("MyPlugin", typeof(bool), typeof(MyPlugin))] 
public class MyPlugin: IStrategy<bool> 
{ 
    public IStrategyResult<bool> Execute(ISerializable info = null) 
    { 
     bool result; 
     try 
     { 
      // do stuff 
      result = true; 
     } 
     catch (Exception) 
     { 
      result = false; 
     } 

     return new StrategyResult<bool> 
     { 
      Value = result 
     }; 
    } 
} 

Пара вещей, чтобы отметить здесь. Во-первых это RegisterActionAttribute:

[AttributeUsage(AttributeTargets.Class)] 
public sealed class RegisterActionAttribute : Attribute 
{ 
    public StrategyAction StrategyAction { get; } 

    public RegisterActionAttribute(string actionName, Type targetType, Type returnType, params string[] depdencies) 
    { 
     StrategyAction = new StrategyAction 
     { 
      Name = actionName, 
      StrategyType = targetType, 
      ResponseType = returnType, 
      Dependencies = depdencies 
     }; 
    } 
} 

Затем интерфейсы:

public interface IStrategy<T> 
{ 
    IStrategyResult<T> Execute(ISerializable info = null); 
} 

public interface IStrategyResult<T> 
{ 
    bool IsValid { get; set; } 
    T Value { get; set; } 
} 

Все довольно прямо вперед. Цель здесь - просто прикрепить некоторые метаданные к классу при его загрузке. Загрузка происходит через единицу с помощью обертки, которая просто загружает сборки в каталоге bin с помощью шаблона поиска файлов и добавляет его в одноэлементный класс с коллекцией StrategyActions. Мне не нужно вставлять весь код единицы здесь, поскольку я знаю, что он работает, и регистрирует и разрешает сборки.

Так что теперь, чтобы мясо вопроса. У меня есть функция на синглетоне, которая выполняет действия. Они применяются с Unity.Interception HandlerAttributes и передается строка, как это (я могу отправить код для этого, но я не думаю, что это было уместно):

[ExecuteAction("MyPlugin")] 

обработчик вызывает функцию следующие выполнения на одноэлементных класса для «выполнения» функций, которые зарегистрированы (добавлены в коллекцию).

public dynamic Execute(string action, params object[] parameters) 
{ 
    var strategyAction = _registeredActions.FirstOrDefault(a => a.Name == action); 
    if (strategyAction == null) 
     return null; 

    var type = typeof (IStrategy<>); 
    var generic = type.MakeGenericType(strategyAction.StrategyType); 

    var returnType = typeof (IStrategyResult<>); 
    var genericReturn = returnType.MakeGenericType(strategyAction.ResponseType); 

    var instance = UnityManager.Container.Resolve(generic, strategyAction.Name); 
    var method = instance.GetType().GetMethod("Execute"); 

    return method.Invoke(instance, parameters); 
} 

Это выполнение заворачивает в перечислителях вызова, который возвращает коллекцию результатов, рассортировывающую управлять зависимостями и что нет (см. Ниже) Эти значения ссылаются вызывающим абонентом, используя свойство Value ISTrategyResult {T} для выполнения различных действий, определенных другими бизнес-правилами.

public List<dynamic> ExecuteQueuedActions() 
    { 
     var results = new List<dynamic>(); 
     var actions = _queuedActions.AsQueryable(); 
     var sortedActions = TopologicalSort.Sort(actions, action => action.Dependencies, action => action.Name); 
     foreach(var strategyAction in sortedActions) 
     { 
      _queuedActions.Remove(strategyAction); 
      results.Add(Execute(strategyAction.Name)); 
     } 
     return results; 
    } 

Теперь помните, что это работает, и я получаю тип возвращаемого значения, указанный атрибутом RegisterAction плагинов. Как вы можете видеть, я захватываю тип плагина и тип возвращаемого значения. Я использую «общую» переменную, чтобы разрешить тип с единством с помощью MakeGenericType, который отлично работает. Я также создаю общий тип возвращаемого типа, основанный на типе из коллекции.

Что мне здесь не нравится, так это использовать динамику, чтобы вернуть это значение функции. Я не могу найти способ вернуть это как IStrategyResult {T}, потому что, очевидно, вызывающий объект для «dynamic Execute (...») не может во время выполнения использовать возвращаемый тип функции. вызов Execute с вызовом MakeGenericMethod, поскольку у меня на самом деле есть ожидаемый тип StrategyAction. Было бы здорово, если бы я мог каким-то образом выяснить, как это сделать, чтобы вернуть строго типизированный результат IStrategyResult {T} при определении типа T во время вызова .

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

+1

Что вы будете делать с результатами, если у вас есть? Похоже, вам нужен соответствующий интерфейс 'IHandler ' для обработки результатов. Затем вы можете просто скомпоновать методы 'Execute' и' Handle' как 'Action'. – Lee

+0

Я добавил функцию обертки, которую я могу использовать для агрегирования результатов вызовов, надеюсь, что ответит на ваш вопрос. Или, может быть, я не понимаю ваш ответ? – Brandon

+0

Думаю, я понимаю, о чем вы говорите, что имеет смысл. Тем не менее, учитывая, что мне нужно вернуть некоторые из этих значений, я думаю, столкнулся бы с той же проблемой знания типа T в вызывающем. Я знаю, что это неотъемлемая проблема с этим. Я предполагаю, что это просто природа зверя, и единственное, что я могу сделать, это добавить некоторую проверку/кастинг в вызывающем, чтобы соответствующим образом разобраться с результатами. Охлаждающая (но опасная) вещь о динамике - это то, что мне не нужно ссылаться на результат. Но когда-то это в динамике своего рода в философски «серой» области. – Brandon

ответ

7

Вам нужен более широкий рефакторинг, чем просто выяснить, как вызвать ваш плагин.

Атрибут [RegisterAction] не нужен, чтобы удерживать targetType и returnType, эти параметры для атрибута могут легко выйти из синхронизации с кодом, сделав их потенциальным отверстием для падения.

Тогда думает, с другой стороны вашей установки: как вы уничтожаете данные, что вы делаете с вашими IStrategyResult<> - это делает действительно должно быть общими или есть конкретный способ, которым Вы могли бы инкапсулировать тип Результаты? Я не могу представить себе систему плагинов, которая возвращает «ничего» хосту. Подсказка действительно находится в вашем dynamic Execute(...) - ваши параметры и ваш результат оба потеряли свою сильную типизацию, показывая вам, что сильная печать плагина ничем не помогает. Просто используйте object или - лучше - сделать StrategyResult класс вместо текущего интерфейса и обеспечивает то, что свойства необходимо там (я добавил несколько несерьезных примеров), такие как:

public class StrategyResult{ 
    public object Result{get;set;} 
    public Type ResultType {get;set;} 

    // frivolous examples 
    public bool IsError {get;set;} 
    public string ErrorMessage {get;set;} 

    // really off-the-wall example 
    public Func<StrategyHostContext,bool> ApplyResultToContext {get;set;} 

    public StrategyResult(){ 
    } 

    public StrategyResult FromStrategy(IStrategy strategy){ 
    return new StrategyResult{ 
     ResultType = strategy.ResultType 
    } 
    } 

    public StrategyResult FromStrategyExecute(IStrategy strategy, ISerializable info = null){ 
    var result = FromStrategy(strategy); 
    try{ 
     strategy.Execute(info); 
    } catch (Exception x){ 
     result.IsError = true; 
     result.ErrorMessage = x.Message; 
    } 
    } 
} 

Тогда ваш IStrategy становится:

public interface IStrategy{ 
    Type ResultType {get;} 
    void Initialize(SomeContextClassMaybe context); 
    StrategyResult Execute(ISerializable info = null); 
} 

Вы также можете изменить атрибут, чтобы сделать его более эффективным для загрузки больших плагинов:

[AttributeUsage(AttributeTargets.Assembly)] 
public sealed class AddinStrategyAttribute : Attribute 
{ 
    public Type StategyType {get; private set;} 
    public AddinStrategyAttribute(Type strategyType){ 
    StrategyType = strategyType; 
    } 
} 

...и использовать атрибут как так:

[assembly:AddinStrategy(typeof(BoolStrategy))] // note it's outside the namespace 
namespace MyNamespace{ 
    public class BoolStrategy: IStrategy{ 
     public Type ResultType { get{ return typeof(bool);}} 
     public void Initialize (SomeContextClassMaybe context){ 
     } 
     public StrategyResult Execute(ISerializable info = null){ 
     return StrategyResult.FromStrategyExecute(this,info); 
     } 
    } 
} 
+0

Так что извините за поздний отклик всем, я в конечном итоге немного надел другой проект. Теперь, наконец, вернемся к этому. – Brandon

+0

Сильная типизация плагина была предназначена как «подсказка», так сказать, для ускорения обработки. Я не хотел делать кучу условных блоков (что ограничивает расширяемость плагина без необходимости внесения изменений в ядро) или отражения в вызывающем абоненте (что может замедлить его с учетом объема вызовов плагинов, которые могут быть Так как эта плагиновая система должна работать, она может потенциально вызвать эти вещи в сотни и тысячи раз по мере ее перечисления через коллекцию. – Brandon

+0

Для достижения обоих (избегайте длинных ifs и производительности) добавьте метод внутри плагина, который знает, как приклеить свои собственные данные к контекстным данным - «ApplyResultToContext» для этой цели специально –

2

Вы попали в этот рассол, предоставив свой конструктор RegisterActionAttribute аргумент returnType.Поскольку у вас есть только один метод Execute(), вы вынуждены иметь дело с тем, что тип возврата может быть разных типов.

Использование dynamic примерно такое же хорошее, как и оно. Вы можете сделать Execute() общим, но тогда вам придется иметь дело с несоответствием между его параметром type и ResponseType атрибута. Не тот, который компилятор может поймать, это не удается во время выполнения. Это не общий.

Честно говоря, это сильно звучит как одна гибкость слишком много. Исходя из того, что интерпретация точки неправильного типа возврата неверна, результат «действия регистрации» является скорее логическим. Это сработало, или это не сработало. И на самом деле так, как вы его реализовали, ваш первый фрагмент плагина возвращает bool.

С очень высокими коэффициентами, что вы не должны использовать bool. Неудача должна произойти, вы бы выбрали исключение.

2

Почему бы не определить супер интерфейс IStrategyResult как это:

interface IStrategyResult 
{ 
    Type ReturnType { get; } 
} 

interface IStrategyResult<T> : IStrategyResult 
{ 
    // your code here 
} 

Затем определите ваш выполнить так:

public IStrategyResult Execute(string action, params object[] parameters)

И ваш StrategyResult : IStrategyResult<T> класс установить свойство возвращать typeof(T)

По соглашению вы можете предположить (или en сила с использованием наследования класса abstract StrategyResult<T> : IStrategyResult<T>) T будет таким же, как и у ReturnType, свойства неосновного интерфейса IStrategyResult.

5

Если предположить, что вызывающий ExecuteActions не имеет каких-либо знаний о T в любом из плагинов или результатов и должны работать с dynamic или object в любом случае, то следующий может работать:

Инфраструктура:

public interface IStrategy 
{ 
    IStrategyResult Execute(ISerializable info = null); 
} 

public interface IStrategyResult 
{ 
    bool IsValid { get; } 
    dynamic Value { get; } 
} 

public class StrategyResult<T> : IStrategyResult 
{ 
    public T Value { get; private set; } 
    public StrategyResult(T value) { this.Value = value; } 

    public bool IsValid { get { throw new NotImplementedException(); } } 

    dynamic IStrategyResult.Value { get { return this.Value; } } 

} 

[AttributeUsage(AttributeTargets.Class)] 
public sealed class RegisterActionAttribute : Attribute 
{ 
    public List<string> Dependencies { get; private set; } 

    public RegisterActionAttribute(params string[] depdencies) 
    { 
     this.Dependencies = new List<string>(depdencies); 
    } 
} 

public class StrategyAction 
{ 
    public string Name; 
    public List<string> Dependencies; 
} 

public abstract class BasePlugin<T> : IStrategy 
{ 
    public IStrategyResult Execute(ISerializable info = null) 
    { 
     return new StrategyResult<T>(this.execute(info)); 
    } 
    protected abstract T execute(ISerializable info); 
} 

Пример плагин:

[RegisterAction] 
public class MyFirstPlugin: BasePlugin<bool> 
{ 
    protected override bool execute(ISerializable info = null) 
    { 
     try 
     { 
      // do stuff 
      return true; 
     } 
     catch (Exception) 
     { 
      return false; 
     } 
    } 
} 

[RegisterAction("MyFirstPlugin")] 
public class MySecondPlugin: BasePlugin<string> 
{ 
    protected override string execute(ISerializable info = null) 
    { 
     try 
     { 
      // do stuff 
      return "success"; 
     } 
     catch (Exception) 
     { 
      return "failed"; 
     } 
    } 
} 

Пример исполнения двигателя:

public class Engine 
{ 

    public List<StrategyAction> registeredActions = new List<StrategyAction>(); 
    private List<StrategyAction> queuedActions  = new List<StrategyAction>(); 

    public IStrategyResult Execute(string action, ISerializable info = null) 
    { 
     if (this.registeredActions.FirstOrDefault(a=>a.Name == action) == null) return null; 

     // This code did not appear to be used anyway 
     //var returnType = typeof (IStrategyResult<>);            //var genericReturn = returnType.MakeGenericType(strategyAction.ResponseType); 

     var instance = (IStrategy) UnityManager.Container.Resolve(typeof(IStrategy), action); 

     return instance.Execute(info); 
    } 

    public List<IStrategyResult> ExecuteQueuedActions() 
    { 
     var results   = new List<IStrategyResult>(); 
     var actions   = this.queuedActions.AsQueryable(); 
     var sortedActions = TopologicalSort.Sort(actions, action => action.Dependencies, action => action.Name); 
     foreach(var strategyAction in sortedActions) 
     { 
      this.queuedActions.Remove(strategyAction); 
      results.Add(Execute(strategyAction.Name)); 
     } 
     return results; 
    } 

} 

Обратите внимание, что, когда модули загружены, RegisterActionAttribute информацию вместе с именем типа подключаемого модуля, загруженного необходимости быть объединены в StrategyAction инстанции и загружается в поле registeredActions двигателя.

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

Смежные вопросы