PostSharp4.3/Custom Patterns/Developing Custom Aspects/Developing Simple Aspects/Injecting Behaviors into Iterators
Injecting Behaviors into Iterators

Iterator methods are methods that can return several values. In C#, values are produced using the yield return statement. Iterators return a value of type IEnumerable or IEnumerator. At build time, the compiler performs a complex transformation of the code. The original iterator logic is moved to a different type implementing the IEnumerator interface, and the original method body is replaced by just a few lines of code instantiated this class.

By default, all aspects applied on an iterator method are actually applied to the method instantiating the iterator class. However, there are times when you want to actually add an aspect to the iterator itself. We will see how this is possible using the OnMethodBoundaryAspect aspect. For a more general information about using OnMethodBoundaryAspect in non-async methods, see Injecting Behaviors Before and After Method Execution.

You have two options when applying OnMethodBoundaryAspect to an iterator method. The first option is to apply the aspect to the method that only creates and returns the iterator instance. The second option is to apply the aspect to the actual code that implements the logic inside the iterator. When using the second option you can also inject behaviors before and after your yield return statements and access the current yield value.

This topic contains the following sections:

Applying the aspect to the method instantiating the iterator

You can control how the aspect is applied to the iterator by setting the boolean ApplyToStateMachine property. Set this property to false on your aspect to apply the aspect only to the code instantiating the iterator. You can set the property in the constructor if you don’t want to set it explicitly every time the aspect is used.

In the following procedure, we demonstrate how to inject behaviors into the method instantiating the iterator. For this purpose, we create a simplified caching aspect that stores all the values returned by iterator as a single array in cache. This aspect can be applied to any iterator returning IEnumerable.

To inject behaviors into the method instantiating the iterator:

  1. Create an aspect class and inherit OnMethodBoundaryAspect. Annotate the class with the [PSerializableAttribute] custom attribute.

  2. Set the ApplyToStateMachine property to false in your constructor.

    C#
    [PSerializable]
    public class CacheAttribute : OnMethodBoundaryAspect
    {
        public CacheResultAttribute()
        {
            ApplyToStateMachine = false;
        }
    }
    Note Note

    The default value of the ApplyToStateMachine property is false unless the OnYield(MethodExecutionArgs) or OnResume(MethodExecutionArgs) advice is implemented. However, PostSharp will emit a warning whenever OnMethodBoundaryAspect is applied to an iterator without setting the ApplyToStateMachine property.

  3. Override some of the virtual methods of the OnMethodBoundaryAspect class, according to your requirements. For more information about the corresponding virtual methods, see Injecting Behaviors Before and After Method Execution.

    The following snippet shows the overrides of OnEntry(MethodExecutionArgs) and OnSuccess(MethodExecutionArgs) methods for the CacheAttribute class.

    C#
    [PSerializable]
    public class CacheAttribute : OnMethodBoundaryAspect
    {
        public CacheAttribute()
        {
            ApplyToStateMachine = false;
        }
    
        public override void OnEntry( MethodExecutionArgs args )
        {
            object cachedValue = MemoryCache.Default[args.Method.Name];
            if ( cachedValue != null )
            {
                args.ReturnValue = cachedValue;
                args.FlowBehavior = FlowBehavior.Return;
            }
        }
    
        public override void OnSuccess( MethodExecutionArgs args )
        {
            object[] elements = ( (IEnumerable) args.ReturnValue ).OfType<object>().ToArray();
            MemoryCache.Default[args.Method.Name] = elements;
            args.ReturnValue = elements;
        }
    }
  4. Apply your custom attribute to the target methods. Below you can see the [Cache] attribute applied to the TestCaching method.

    C#
    [Cache]
    public IEnumerable TestCaching()
    {
        yield return 1;
        yield return 2;
        yield return 3;
    }
    Note Note

    You can also set the ApplyToStateMachine property value when applying the attribute to a method, for instance using the syntax [MyAspect( ApplyToStateMachine = false )]. This can be useful if it makes sense to apply this aspect to both the state machine or the instantiation method.

Thanks to the aspect, whenever the TestCaching method is called, the CacheAttribute.OnEntry method executes first. It checks whether the result value is already stored in the cache. If the value already exists, then it is immediately returned back to the caller. Otherwise, the execution continues normally and a new iterator instance is created.

The CacheAttribute.OnSuccess method is called before the newly created iterator instance is returned back to the caller. The aspect's method enumerates all the iterator values using ToArray method and stores the resulting array in cache. The array is also returned to the caller instead of the original iterator instance.

Applying the aspect to the iterator itself

In many cases it can be useful to inject behavior into the user code of the iterator implementation itself. You can accomplish this by setting the ApplyToStateMachine property to true on your OnMethodBoundaryAspect.

For example, you may want to write a log entry each time the enumeration of the iterator begins and ends instead of logging only the creation of the iterator instance. In this section, we will create a simple logging aspect and show how to apply it correctly to iterators.

To inject behaviors into the iterator itself:

  1. Create an aspect class and inherit OnMethodBoundaryAspect. Annotate the class with the [PSerializableAttribute] custom attribute.

    C#
    [PSerializable]
    public class LogIteratorAttribute : OnMethodBoundaryAspect
    {
    }
  2. Override some of the virtual methods of the OnMethodBoundaryAspect class, according to your requirements. For more information about the corresponding virtual methods, see Injecting Behaviors Before and After Method Execution.

    The following snippet shows the overrides of OnEntry(MethodExecutionArgs) and OnSuccess(MethodExecutionArgs) methods for the LogIteratorAttribute class.

    C#
    [PSerializable]
    public class LogIteratorAttribute : OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionArgs args)
        {
            Console.WriteLine("The enumeration of {0} started.", args.Method.Name);
        }
    
        public override void OnSuccess(MethodExecutionArgs args)
        {
            Console.WriteLine("The enumeration of {0} finished.", args.Method.Name);
        }
    }
  3. Apply your custom attribute to the target iterator methods and set the ApplyToStateMachine property to true. Below you can see the [LogIterator] attribute applied to the TestLogging method.

    C#
    [LogIterator( ApplyToStateMachine = true )]
    public IEnumerable<int> TestLogging()
    {
        yield return 1;
        yield return 2;
        yield return 3;
    }
    Note Note

    You can set the ApplyToStateMachine property in the constructor if you don’t want to set it explicitly every time the aspect is used.

Whenever the TestLogging method is called, it creates a new iterator instance. Then the caller starts the enumeration of the iterator and immediately the LogIteratorAttribute.OnEntry method is invoked. The method writes the first log entry and the enumeration continues.

The LogIteratorAttribute.OnSuccess method is called after the enumeration of the iterator has completed. The method writes the second log entry and the control is given back to the caller.

The snippet below shows the enumeration of our sample iterator and the output written to the console.

C#
foreach (int n in TestLogging()) Console.WriteLine(n);
The enumeration of TestLogging started.
1
2
3
The enumeration of TestLogging finished.
Adding behaviors around the "yield return" statement

When applying the aspect to the iterator itself, you can also inject behaviors before and after the yield return statements in that iterator. To achieve that, you need to override the OnYield(MethodExecutionArgs) and OnResume(MethodExecutionArgs) methods of the OnMethodBoundaryAspect class.

The OnYield(MethodExecutionArgs) method is invoked after the iterator yields the control flow, as the result of the yield return statement. In this method you can also access and modify the current yield value by using the YieldValue property.

The OnResume(MethodExecutionArgs) method is invoked when the iterator resumes execution at the point after the yield return statement.

Note Note

Note, that implementing OnYield(MethodExecutionArgs) or OnResume(MethodExecutionArgs) method also automatically sets the default value of ApplyToStateMachine property to true and quiets the warning generated by PostSharp.

In the next steps we will create a sample aspect that counts the number of the not-null elements returned by the iterator and writes the result to the console.

To inject behaviors around the "yield return" statement:

  1. Create an aspect class and inherit OnMethodBoundaryAspect. Annotate the class with the [PSerializableAttribute] custom attribute.

    C#
    [PSerializable]
    public class NotNullCounterAttribute : OnMethodBoundaryAspect
    {
    }
  2. Override some of the virtual methods of the OnMethodBoundaryAspect class, according to your requirements. For more information about the corresponding virtual methods, see Injecting Behaviors Before and After Method Execution.

    The following snippet shows the overrides of OnEntry(MethodExecutionArgs) and OnExit(MethodExecutionArgs) methods for the NotNullCounterAttribute class.

    C#
    [PSerializable]
    public class NotNullCounterAttribute : OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionArgs args)
        {
            // Initialize and store the couner for use in other method.
            args.MethodExecutionTag = 0;
        }
    
        public override void OnExit(MethodExecutionArgs args)
        {
            Console.WriteLine("{0} returned {1} not-null values.", args.Method.Name, args.MethodExecutionTag);
        }
    }
  3. Override the OnYield(MethodExecutionArgs) method to add functionality at the point where the iterator yields the next value. This method is invoked when the execution of the iterator is paused and the caller takes over the control flow.

    The following code snippet shows the OnYield(MethodExecutionArgs) method override for the NotNullCounterAttribute class.

    C#
    public override void OnYield(MethodExecutionArgs args)
    {
        if (args.YieldValue != null)
        {
            args.MethodExecutionTag = ((int) args.MethodExecutionTag) + 1;
        }
    }
  4. Apply your custom attribute to the target methods. Below you can see the [NotNullCounter] attribute applied to the TestLogging method.

    C#
    [NotNullCounter]
    public IEnumerable<string> TestLogging()
    {
        yield return "One";
        yield return null;
        yield return "Two";
    }

Thanks to the aspect, we will initialize the counter when the enumeration of the TestLogging starts, increase the counter each time a not-null value is returned, and finally write the result when the enumeration completes.

The snippet below shows the enumeration of our sample iterator and the output written to the console.

C#
foreach (string s in TestLogging()) Console.WriteLine(s ?? "<null>");
One
<null>
Two
TestLogging returned 2 not-null values.
See Also