Могу ли я использовать шаблон декоратора для обертывания тела метода?

У меня есть множество методов с различными сигнатурами. Эти методы взаимодействуют с хрупким соединением данных, поэтому мы часто используем вспомогательный класс для выполнения повторных попыток/повторных подключений и т.д. Так же:

MyHelper.PerformCall( () => { doStuffWithData(parameters...) });

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

[InteractsWithData]
protected string doStuffWithData(parameters...)
{
     // do stuff...
}

И затем, по существу, всякий раз, когда вызывается doStuffWithData, тело этого метода будет передано как Action to MyHelper.PerformCall(). Как это сделать?

Ответы

Ответ 1

. Атрибуты .NET - это метаданные, а не декораторы/активные компоненты, которые автоматически вызываются. Невозможно достичь такого поведения.

Вы можете использовать атрибуты для реализации декораторов, поместив код декоратора в класс Attribute и вызовите метод со вспомогательным методом, который вызывает метод в классе Attribute с помощью Reflection. Но я не уверен, что это будет большим улучшением, чем просто вызвать "метод декоратора".

"Декоратор-Атрибут":

[AttributeUsage(AttributeTargets.Method)]
public class MyDecorator : Attribute
{
    public void PerformCall(Action action)
    {
       // invoke action (or not)
    }
}

Метод:

[MyDecorator]
void MyMethod()
{
}

Использование:

InvokeWithDecorator(() => MyMethod());

Вспомогательный метод:

void InvokeWithDecorator(Expression<Func<?>> expression)
{
    // complicated stuff to look up attribute using reflection
}

Взгляните на рамки для аспектно-ориентированного программирования на С#. Они могут предложить то, что вы хотите.

Ответ 2

Итак, я просто пошел на сессию АОП в эти выходные, и вот способ сделать это с PostSharp:

[Serializable]
public class MyAOPThing : MethodInterceptionAspect
{
    public override void OnInvoke(MethodInterceptionArgs args)
    {
        Console.WriteLine("OnInvoke! before");
        args.Proceed();
        Console.WriteLine("OnInvoke! after");
    }
}

И затем украсьте методы с помощью [MyAOPThing]. Легко!

Ответ 3

Этот тип проблемы в значительной степени предназначен для решения AOP (аспектно-ориентированного программирования). Такие инструменты, как PostSharp, могут обеспечить сквозные проблемы путем перезаписи скомпилированного кода. Подкаст Scott Hanselman недавно обсуждал AOP, поэтому, возможно, стоит послушать.

Ответ 4

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

Но как насчет использования метода расширения?

class static MyHelper
{
  Wrap<T>(this object service, Action<T> action)
  {
    // check attribute and wrap call
  }

}

использование:

RawFoo foo = ...
foo.Wrap(x => x.doStuffWithData(parameters...));

Это тривиально, но вы не можете убедиться, что Wrap был использован.

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

class Decorator<T>
{
    private T implementor;

    Decorator(T implementor)
    {
      this.implementor = implementor;
    }

    void Perform<T>(Action<T> action)
    {
      // check attribute here to know if wrapping is needed
      if (interactsWithData)
      {
        MyHelper.PerformCall( () => { action(implementor) });
      }
      else
      {
        action(implementor);
      }
    }
}

static class DecoratorExtensions
{
    public static Decorator<T> CreateDecorator<T>(T service)
    {
      return new Decorator<T>(service);
    }
}

Использование:

// after wrapping, it can't be used the wrong way anymore.
ExtendedFoo foo = rawFoo.CreateDecorator();
foo.Perform(x => x.doStuffWithData(parameters...));

Ответ 5

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

Ответ 6

Похоже, что вы хотите, так же, как и поведение контейнера контейнера IoC или платформы тестового бегуна, где он фактически не выполняется из вашей сборки, но запускает динамически выпущенную сборку, построенную на вашем коде. (Более умные люди, чем я назвал этот АОП в других ответах)

Поэтому, возможно, в заглушке для вашего приложения вы можете сканировать другие сборки, создавать эти выпущенные сборки (которые вызывают MyHelper.PerformCall с телом декорированных методов), тогда ваша программа работает против испускаемого кода.

Ни в коем случае я не стану начинать с того, что пытаюсь написать это, не оценивая, может ли какая-то существующая структура AOP выполнить то, что вам нужно. НТН >

Ответ 7

Увидев, что вы хотите добавить строку кода ко всем методам, которым это необходимо, почему бы просто не сделать вызов MyHelper из самого метода, например?

protected string doStuffWithData(parameters...)
{
     MyHelper.PerformCall( () => { doStuffWithDataCore(parameters...) });
}

private string doStuffWithDataCore(parameters...) {
    //Do stuff here
}