Archive

PostSharp 3.1 catches up with two advanced features of the C# compiler: automatic iterators and async methods.  Iterators have been around since NET 3.5. This was the first compiler feature that did not trivially map to MSIL, the level of abstraction at which PostSharp operates. Under the hood, when it meets a iterator, the compiler would generate a class. The parameters and local variables of the method would be transformed into fields, and the body of the method would be generated into the MoveNext method, with multiple exit and entry points.

Before version 3.1, PostSharp did not understand the state machine transformation operated by the compiler. Adding an aspect to an iterator would produce surprising results, because the aspect would actually be applied only to the code that instantiates the state machine class. For instance, an OnException advise would never get invoked because instantiating the state machine is unlikely to throw any exception. Conversely, any exception thrown by the iterator would never be captured because the exception was technically (at MSIL level) thrown by the MoveNext method of the state machine class. The workaround was easy: add an aspect to this MoveNext method. The availability of this workaround was perhaps the reason why users did not complain that PostSharp lacks this ability.

Things became more problematic with async methods. The compiler does the same kind of magic as with iterators, but a more advanced one. With async methods, exceptions thrown by user code would get “transparently” intercepted from within the MoveNext method and assigned to the Task object – before they get any chance to get intercepted by our aspect. Thus, the need for proper support from PostSharp became more urgent. No wonder if this became our number-one feature request on User Voice. We were not able to ship the feature in PostSharp 3.0 for a lot of embarrassing reasons, but now it’s out!

The old, backward-compatible way

PostSharp has been around for 9 years. One of our major concerns has always been to never break backward compatibility. The problem is actually not to ensure that your old code still builds after you upgrade to PostSharp. The real challenge is to guarantee that your code will behave identically.

To ensure behavioral backward compatibility, we had to take this design decision: if you apply an OnMethodBoundaryAspect to an iterator or async method, by default, it won’t be applied to the state machine.

However, we still think that the backward-compatible behavior is odd and that new users would really expect the aspect to be applied to the state machine. Therefore, a warning will be emitted whenever an OnMethodBoundaryAspect is applied to an async or iterator method. To turn off the warning, you have to set the aspect property ApplyToStateMachine to false if you want to maintain the backward-compatible behavior. You can set the property in the constructor if you don’t want to set it explicitly every time the aspect is used.

The following aspect exhibits the backward-compatible behavior of the OnMethodBoundary aspect:

[PSerializable]
class MyAspect : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
        Console.WriteLine("OnEntry");
    }

    public override void OnSuccess(MethodExecutionArgs args)
    {
        Console.WriteLine("OnSuccess({0})", args.ReturnValue);
    }

    public override void OnExit(MethodExecutionArgs args)
    {
        Console.WriteLine("OnExit");
    }

    public override void OnException(MethodExecutionArgs args)
    {
        Console.WriteLine("OnException({0})", args.Exception.Message);
    }
}

Let’s apply this aspect to an iterator:

static void PrintFruits(bool throwException)
{
    try
    {
        foreach (string fruit in GetFruits(throwException))
        {
            Console.WriteLine("Received: " + fruit);
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("Exception: " + e.Message);
    }
}

[MyAspect(ApplyToStateMachine = false)]
static IEnumerable GetFruits(bool throwException)
{
    yield return "blackcurrant";
    yield return "pomegranate";
    if ( throwException ) throw new Exception("Rotten fruit.");
    yield return "pineapple";
}

Here is the output of this code with the success flow:

OnEntry
OnSuccess(Program+<GetFruits>d__0)
OnExit
Received: blackcurrant
Received: pomegranate
Received: pineapple

You can see that the OnSuccess advice is called before the enumerator has produced the first result, and that the return value printed by the OnSuccess advice does not make sense.

And here is the output with the exception flow:

OnEntry
OnSuccess(Program+<GetFruits>d__0)
OnExit
Received: blackcurrant
Received: pomegranate
Exception: Rotten fruit.

As you can see, the OnException handler is never hit.

Applying the aspect to the state machine

Things get very different when you set the ApplyToStateMachine property to true. Just modify the property and the code above will produce the following output:

OnEntry
Received: blackcurrant
Received: pomegranate
Received: pineapple
OnSuccess()
OnExit

The exception flow is the following:

OnEntry
Received: blackcurrant
Received: pomegranate
OnException(Rotten fruit.)
Exception: Rotten fruit.

As you can see, the behavior is now “as expected”, i.e. it is consistent with the level of abstraction of the source code. The previous behavior was consistent with the level of abstraction of MSIL, and this is why it was less useful.

New advices: OnYield and OnResume

Additionally to applying the aspect to the state machine instead of the method that merely instantiates it, PostSharp 3.1 brings two new advices: OnYield and OnResume. These advices are defined on the new interface IOnStateMachineBoundaryAspect. If you want to use them, you need to have your aspect class implement this interface.

Note that implementing the IOnStateMachineBoundaryAspect has the side effect of settings the default value of the ApplyToStateMachine property to true and to quiet the warning that is otherwise displayed then this property is not set. This is because, if your code implements IOnStateMachineBoundaryAspect, we trust we can put usability prior to backward compatibility.

But let’s go back to the advices themselves. Interestingly, they apply identically – with exactly the same semantics – to both iterator and async methods. In short, OnYield is invoked when the state machine yields the control flow, i.e. when the control flow temporary leaves the state machine to the caller. OnResume is invoked when the state machine gains back the control flow.

With iterators

Let’s see this more concretely, first on iterators. OnYield is invoked after the yield return statement, before the control flow gets back to the consumer of the iterator. OnResume is then invoked when the consumer calls MoveNext. Note that the first time the consumer calls MoveNext, the OnEntry advice is invoked. Also, when the iterators terminates using yield break or simply by letting the control flow fall back, the OnSuccess advice is invoked after OnYield.

What if you want to know which value has just been yielded? Simply read the MethodExecutionArgs.YieldValue property. You can also write this property if you want to change the returned value.

Let’s update our aspect to add tracing of OnYield and OnResume events:

[PSerializable]
class MyAspect : OnMethodBoundaryAspect, IOnStateMachineBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
        Console.WriteLine("OnEntry");
    }

    public override void OnSuccess(MethodExecutionArgs args)
    {
        Console.WriteLine("OnSuccess({0})", args.ReturnValue);
    }

    public override void OnExit(MethodExecutionArgs args)
    {
        Console.WriteLine("OnExit");
    }

    public override void OnException(MethodExecutionArgs args)
    {
        Console.WriteLine("OnException({0})", args.Exception.Message);
    }

    public void OnResume(MethodExecutionArgs args)
    {
        Console.WriteLine("OnResume");
    }

    public void OnYield(MethodExecutionArgs args)
    {
        Console.WriteLine("OnYield({0})", args.YieldValue);
    }
}

The program output is now the following:

OnEntry
OnYield(blackcurrant)
Received: blackcurrant
OnResume
OnYield(pomegranate)
Received: pomegranate
OnResume
OnYield(pineapple)
Received: pineapple
OnResume
OnSuccess()
OnExit
-------------
OnEntry
OnYield(blackcurrant)
Received: blackcurrant
OnResume
OnYield(pomegranate)
Received: pomegranate
OnResume
OnException(Rotten fruit.)
Exception: Rotten fruit.

With async methods

The behavior of OnYield and OnResume is similar for async methods than for iterators. OnYield and OnResume are invoked upon execution of the await keyword: OnYield gets called when a wait begins, OnResume when a wait ends.

Note there are cases where the await keyword does not result into an execution of the OnYield/OnResume sequence: when the awaited-for task completes synchronously, the execution of the method continues without going through OnYield and OnResume. This is logical if you count that in this case the state machine really does not yield the control flow.

The YieldValue property is not available for async methods.

Let’s test our aspect on the following code:

[MyAspect]
static async Task<string> TimerMethod()
{
    for (int i = 3; i >= 0; i--)
    {
        Console.WriteLine(i + " green bottles");
        await Task.Delay(100);
    }

    return "Done";
}

The output of this program is the following:

OnEntry
3 green bottles
OnYield()
OnResume
2 green bottles
OnYield()
OnResume
1 green bottles
OnYield()
OnResume
0 green bottles
OnYield()
OnResume
OnSuccess()
OnExit

Limitations

Note that the following limitations apply when an aspect is applied to a state machine (whether the additional advices OnYield and OnResume are applied or not):

  • The control flow cannot be changed (the MethodExecutionArgs.FlowBehavior property is ignored).
  • The return value cannot be read or changed (the MethodExecutionArgs.ReturnValue is ignored).

Aspect Composition

Perhaps it goes without saying, but this is never trivial to implement: you can apply many aspects to state machines, apply state-machine-aware and -unaware aspects to a method, and do strange combinations of aspects. This is all supposed to work – and tested.

Use Cases

I believe the new features are very useful in the following use cases:

  • Call stack reconstruction: remember the stack call on entry so that it can be meaningfully displayed if an exception occurs after resume of the async method. Otherwise, you would just see that the call stack of the exception comes from the thread pool.
  • Iterator logging: you can now log the values returned by the iterator.
  • Profiling: you can now accurately compute the time taken by an iterator or async method to complete.
  • Context switching: ensure that the value of some thread-static fields are preserved and meaningful in all parts of a state machine.

Summary

The OnMethodBoundaryAspect can now be applied to state machines like async and iterator methods, and your aspect will not be applied to the state machine itself instead of just to instructions that instantiate the state machine. There is a new interface IOnStateMachineBoundaryAspect with two new advices: OnYield and OnResume. The new feature is designed to be backward compatible with the old (and odd) behavior. You will need to manually set the ApplyToStateMachine property if you want to get rid of the attention-to-odd-but-backward-compatible-behavior warning.

I think this is an exciting feature, and it required a lot of engineering work just to make it work and integrate well with other features of PostSharp.

A last word: PostSharp 3.1 is still beta and there’s still room to improve the API. I’m anxious to hear your feedback so we can take into account before sealing the new feature.

Happy PostSharping!

-gael

Today we’re excited to announce the public availability of PostSharp 3.1 Preview. You can download the new Visual Studio tooling from our website, our update your packages using NuGet Package Manager. In the latter case, make sure to enable the “Include Prereleases” option.

PostSharp 3.1 includes the following features:

  • Support for state machines (async and iterators)
  • Improved build performance
  • Resolution of file and line of error messages
  • Solution-level policies (and a smarter configuration system)
  • Indentation in logging

PostSharp 3.1 is a free upgrade for all customers with a valid support subscription. The license key of PostSharp 3.0 will work.

Support for state machines

When you applied an OnMethodBoundaryAspect to a method that was compiled into a state machine, whether an iterator or an async method, the code generated by PostSharp would not be very useful:  the aspect would just be applied to the method that implements the state machine. An OnException advise had no chance to get ever fired.

PostSharp 3.1 gets much smarter. OnMethodBoundaryAspect now understands that is being applied to a state machine, and works as you would expect. To enable the new behavior, you need to set the OnMethodBoundaryAspect.ApplyToStateMachine property to true. It is false by default for backward compatibility.

But there is more: the aspects define two advises: OnYield and OnResume. For the sake of backward compatibility, we could not add them to the IOnMethodBoundaryAspect interface, so we defined a new interface IOnStateMachineBoundaryAspect with these two methods.

More blogging about this later.

To discover more on your own, try to apply the following aspect to an async method or an iterator:

[Serializable]
public class MyAspect : OnMethodBoundaryAspect, IOnStateMachineBoundaryAspect
{
    public static StringBuilder Trace = new StringBuilder();

    public override void OnEntry(MethodExecutionArgs args)
    {
        Console.WriteLine("OnEntry");
    }

    public void OnResume(MethodExecutionArgs args)
    {
        Console.WriteLine("OnResume");
    }

    public void OnYield(MethodExecutionArgs args)
    {
        Console.WriteLine("OnYield");
    }

    public override void OnSuccess(MethodExecutionArgs args)
    {
        Console.WriteLine("OnSuccess");
    }

    public override void OnExit(MethodExecutionArgs args)
    {
        Console.WriteLine("OnExit");
    }
}

Read more about this feature.

Improved build performance

The first time you compile a project using a specific build of PostSharp, you will be proposed to install itself into GAC and create native images.

Doing will decrease build time of a fraction of a second for each project. A potentially substantial gain if you have a lot of projects. You can uninstall these images at any time from PostSharp options in Visual Studio.

Under the cover, PostSharp will just install itself in GAC using gacutil and will generate native images using ngen.  The feature does not affect build servers or cloud builds, so only build time on developers’ workstations will be improved.

Keep in mind that your end-users will not have PostSharp installed in GAC, so you still need to distribute PostSharp.dll.

Resolution of file and line of error messages

When previous versions of PostSharp had to report an error or a warning, it would include the name of the type and/or method causing the message, but was unable to determine the file and line number.

You can now double-click on an error message in Visual Studio and you’ll get to the relevant location for the error message.

I know, it seems like an obvious feature. But it was actually quite complex to implement. PostSharp works as MSIL level and the file that’s supposed to MSIL back to source – the PDB file – won’t tell you where a type, field, or abstract method is implemented. Indeed, it only contains “sequence points”, mapping instructions to lines of code. That is, only the inside of method bodies are mapped to source code.  So, we had to rely on a source code parser to make the missing like ourselves.

This feature is available for the C# language only.

Note that this feature is not yet fully stable. There are still many situations where locations cannot be resolved.

Solution-Level Policies

We made it easier to add a policy to a whole solution. Say you want to add logging to the whole solution. Previously, you had to add the aspect to every project of the solution. Now, you can right-click on the solution and just add it to the solution.

This is not just a UI tweak. To make this scenario possible, we had to do significant work on our configuration subsystem:

  • Support for solution-level configuration files (SolutionName.pssln), additionally to project-level files (ProjectName.psproj).
  • Support for conditional configuration elements.
  • Support for XPath in expressions (instead of only property references as previously).

Thanks to this improvements, you can add not only our own ready-made aspects to the whole solution, but also your own aspects.

The configuration subsystem is currently largely undocumented and there was not a good use case for it until PostSharp 3.0. I’ll blog further about this feature and we’ll update the documentation.

Read more about support for solution-level policies.

Indentation in logging

PostSharp Diagnostics Pattern Library was good at adding a lot of logging, but the log quickly became unreadable because the output was not indented. We fixed that. If the logging back-end supports indentation, we’ll call its Indent or Unindent method. Otherwise, we’ll create indentation using spaces.

Our implementation is thread-safe and still does not require you to add a reference to any PostSharp library at runtime (the reference is required at build time and will be removed).

Improvements in PostSharp 3.0

Note that while working on PostSharp 3.1, we still added some features to PostSharp 3.0. The most important ones are support for Windows 8.1 and Visual Studio 2013. Keeping in pace with changes of development environments is a challenge in itself, and we’re glad this we handled it smoothly, without forcing customers to wait for a new minor version.

Please report any issue on our support forum. We’d love to hear about your feedback.

Happy PostSharping – faster than ever.

-gael

We'd like to invite you to join us for the FREE full-day training in London slated for November 7th, 2013. Learn how to automate design pattern implementation and validation using PostSharp with its creator, Gael Fraiteur.

Here's the agenda:

Morning: ready-made design patterns

09.00-10.00 Introduction to Design Pattern Automation with PostSharp
10.05-10.55 Threading patterns
11.10-11.30 INotifyPropertyChanged
11.30-12.30 Hands-On Exercises
 
Lunch: free catered lunch for all attendees!
 
Afternoon: custom patterns

13.35-14.00 Introduction to Aspect-Oriented Programming
14.00-14.45 Simple aspect types, aspect lifetime, applying aspects to code
14.45-15.15 Hands-On Exercises
15.30-16.00 Aspect composition, composite aspects
16.00-16.45 Code Validation and Analysis
16.50-17.30 Hands-On Exercises

Because space is limited, we will draw 15 attendees at random at the end of October and notify them via email with additional course details.

Sign up for your chance to attend the PostSharp Training today!