Подтвердить что ты не робот

Возврат экземпляра родового типа к функции, разрешенной во время выполнения

Просто, чтобы уточнить, у меня есть работа с использованием динамического и 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")]

Обработчик вызывает следующую функцию выполнения в классе singleton для "выполнения" функций, которые зарегистрированы (добавлены в коллекцию).

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 во время вызова.

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

4b9b3361

Ответ 1

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

Нет необходимости в атрибуте [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 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);
      }
    }
}

Ответ 2

Предполагая, что вызывающий объект 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.

Ответ 3

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

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

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

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

Ответ 4

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

interface IStrategyResult
{
    Type ReturnType { get; }
}

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

Затем определите свой запуск следующим образом:

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

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

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