У меня есть набор методов с переменными подписями. Эти методы взаимодействуют с хрупкой передачей данных, таким образом, мы часто используем класс помощника для выполнения, повторяет/повторно подключает, и т.д. Как так:
MyHelper.PerformCall( () => { doStuffWithData(parameters...) });
И это хорошо работает, но это может сделать код небольшим cluttery. То, что я предпочел бы делать, украшают методы, которые взаимодействуют с передачей данных как так:
[InteractsWithData]
protected string doStuffWithData(parameters...)
{
// do stuff...
}
И затем по существу, каждый раз, когда doStuffWithData
назван, тело того метода было бы передано в как Action
кому: MyHelper.PerformCall()
. Как я делаю это?
Атрибуты .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
}
Взгляните на фреймворки для аспектно-ориентированного программирования на C #. Они могут предложить то, что вы хотите.
Видя, что вы готовы добавить строку кода к каждому методу, который в этом нуждается, почему бы просто не вызвать MyHelper из самого метода, как здесь?
protected string doStuffWithData(parameters...)
{
MyHelper.PerformCall( () => { doStuffWithDataCore(parameters...) });
}
private string doStuffWithDataCore(parameters...) {
//Do stuff here
}
Ознакомьтесь с аспектно-ориентированными фреймворками . Но имейте в виду, что, хотя они скрывают сложность каждого метода, наличие функций AoP может усложнить поддержку вашей программы. Это компромисс.
Этот тип проблем в значительной степени является тем, что AOP (аспектно-ориентированное программирование) стремится решить. Такие инструменты, как PostSharp, могут решить общие проблемы, переписав скомпилированный код. Подкаст Скотта Хансельмана недавно обсуждал АОП, так что, возможно, стоит послушать.
Итак, я только что посетил сеанс АОП в эти выходные, и вот способ сделать это с помощью PostSharp:
[Serializable]
public class MyAOPThing : MethodInterceptionAspect
{
public override void OnInvoke(MethodInterceptionArgs args)
{
Console.WriteLine("OnInvoke! before");
args.Proceed();
Console.WriteLine("OnInvoke! after");
}
}
А затем украсить методы с помощью [MyAOPThing]
. Легкий!
Похоже, то, что вы хотите, похоже на поведение IoC-контейнера или фреймворка для запуска тестов, где он на самом деле не выполняется из вашей сборки, а запускает динамически эмитированную сборку, построенную вокруг вашего кода. (Более умные люди, чем я, называли это AOP в других ответах)
Так что, возможно, в заглушке для вашего приложения вы могли бы просканировать другие сборки, собрать эти эмитированные сборки (которые вызывают MyHelper.PerformCall с телом декорированных методов), а затем ваша программа запускается с эмитированным кодом.
Ни в коем случае я не стал бы начинать путь попытки написать это, не оценив, может ли какой-нибудь существующий фреймворк AOP выполнить то, что вам нужно. HTH>
Без генерации годе вы ничего не сможете с этим поделать. Возможно, вы могли бы улучшить синтаксис.
А как насчет использования метода расширения?
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...));