Can I use the decorator pattern to wrap a method body?

21,968

Solution 1

.NET Attributes are meta-data, not decorators / active components that automatically get invoked. There is no way to achieve this behaviour.

You could use attributes to implement decorators by putting the decorator code in the Attribute class and call the method with a helper method that invokes the method in the Attribute class using Reflection. But I'm not sure this would be a big improvement over just calling the "decorator-method" directly.

"Decorator-Attribute":

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

Method:

[MyDecorator]
void MyMethod()
{
}

Usage:

InvokeWithDecorator(() => MyMethod());

Helper method:

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

Have a look at frameworks for Aspect Oriented Programming in C#. These may offer what you want.

Solution 2

So, I just went to a AOP session this weekend, and here's a way to do it with PostSharp:

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

And then decorate methods with [MyAOPThing]. Easy!

Solution 3

Without the use of code generation, you can't do much against it. You could probably make the syntax better.

But what about using an extension method?

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

}

usage:

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

This is trivial, but you can't make sure that Wrap had been used.

You could implement a generic decorator. This decorator would be used once to wrap the service and then you can't call it without wrapping.

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);
    }
}

Usage:

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

Solution 4

This type of problem is pretty much what AOP (aspect oriented programming) aims to solve. Tools such as PostSharp can provide cross-cutting concerns by re-writing the compiled code. Scott Hanselman's podcast recently discussed AOP, so it might be worth having a listen.

Solution 5

Check out aspect oriented frameworks. But be aware that while they hide complexity in each method, the existence of AoP features could make your program harder to maintain. It's a tradeoff.

Share:
21,968
Matthew Groves
Author by

Matthew Groves

Something about the curly brace speaks to me. Matthew D. Groves is a guy who loves to code. It doesn't matter if it's C#, jQuery, or PHP: he'll submit pull requests for anything. He has been coding professionally ever since he wrote a QuickBASIC point-of-sale app for his parent's pizza shop back in the 90s. He currently works as a Product Marketing Manager for Couchbase. His free time is spent with his family, watching the Reds, and getting involved in the developer community. He is the author of AOP in .NET (published by Manning), a Pluralsight author, and a Microsoft MVP.

Updated on July 09, 2022

Comments

  • Matthew Groves
    Matthew Groves almost 2 years

    I have a bunch of methods with varying signatures. These methods interact with a fragile data connection, so we often use a helper class to perform retries/reconnects, etc. Like so:

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

    And this works fine, but it can make the code a little cluttery. What I would prefer to do is decorate the methods that interact with the data connection like so:

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

    And then essentially, whenever doStuffWithData is called, the body of that method would be passed in as an Action to MyHelper.PerformCall(). How do I do this?