Multicasting: Enhance a group of methods with just one attribute

by Petr Hudeček on 09 Nov 2020

Attribute multicasting, in PostSharp, is a way to apply an aspect (such as method interception) to many types or methods with just one attribute instance. It’s at the core of the ability of PostSharp to reduce the number of lines of code in your project.

In the most basic use case, you annotate a class with a method interception/method boundary attribute and it’s multicast (applied) to all methods in that class, but in this blog post, we’ll go over some more advanced use cases as well.

Multicasting is included in all versions of PostSharp including the free PostSharp Essentials edition, and works also for community add-ins such as ToString and StructuralEquality.

What multicasting means

Suppose MyLog is a method boundary aspect that writes to standard output at the beginning and end of each target method. It could look like this:

[Serializable]
public class MyLog : OnMethodBoundaryAspect
{
  public override void OnEntry(MethodExecutionArgs args)
  {
    Console.WriteLine("Entering");
  }
  public override void OnExit(MethodExecutionArgs args)
  {
    Console.WriteLine("Leaving");
  }
}

A method boundary aspect can target methods only, so if you apply it to a type, PostSharp attribute multicasting will instead cause it to apply to all methods of that type.

In the following example, if you write the code on the left, PostSharp will transform your code at build time as though you actually wrote the code on the right, but you avoided some code duplication.

diagram

Some attributes that enhance your code work on types rather than methods or members. For example, the community add-in ToString applies on types and synthesizes a ToString method for them.

You can use multicasting here as well. If you apply the ToString attribute to your assembly, it will instead apply to all classes in that assembly.

diagram

Note how properties of the add-in are copied to the actual target classes as well.

More precise targeting

We’ve seen that with multicasting, attributes “cascade down” from assemblies to types to members.

diagram

(Whether an attribute is type level or method level is determined by MulticastAttributeUsage. You can see documentation for details.)

But it is possible to choose your targets more precisely. Each multicast attribute has a set of properties whose names begins with Attribute, such as AttributeTargetTypes, which affect multicasting. (Almost all add-ins and all PostSharp aspects are multicast attributes.)

Here’s the MulticastAttribute properties that I find the most useful:

  • AttributeTargetTypes. The attribute is only multicast to types matching this wildcard expression or regex.
  • AttributeTargetMembers. The attribute is only multicast to methods matching this wildcard expression or regex.
  • AttributeTargetTypeAttributes. Only multicast to types which have these properties.
  • AttributeTargetMemberAttributes. Only multicast to methods which have these properties.

I’ll show this on a couple of examples:

[assembly: MyLog(
 AttributeTargetTypes = "*ViewModel", 
 AttributeTargetMemberAttributes = MulticastAttributes.Public)]

This means “Apply MyLog to all public methods of types whose names end in ViewModel.”

[assembly: MyLog(AttributeTargetMembers = "regex:button[0-9]+_Click")]
public class Form1 : Form { ... }

This means “Apply MyLog to all OnClick handlers on Form1 for buttons with default names.”

Even more precise targeting with exclusion

You can also use AttributeExclude in combination with AttributePriority to exclude some targets that you’ve previously included in multicasting. All attributes are processed in the order of AttributePriority and what comes out at the end of this processing becomes the set of targets to which the attribute is applied.

Here’s an example where the first line applies the aspect to all public methods, and the second line excludes getters and setters from the set:

code with exclusions

For more description and details, see our documentation. Make sure not to accidentally use “AspectPriority” which is a different property from AttributePriority, and doesn’t affect multicasting.

Where to use multicasting

In general, multicasting is useful and safe when applying the aspect to an unintended target does not have grave impact on functionality or performance.

I find multicasting the most helpful in the following cases:

  • Logging and profiling. In PostSharp Essentials, you can use PostSharp Logging for free in Developer Mode, and you can also write your own logging aspect (sample) or profiling aspect (sample). You may want to apply logging or profiling to large portions of your codebase, or — especially during debugging — to all methods in selected classes.
  • ToString. The community add-in ToString is useful in many classes. You can multicast ToString to absolutely every class (it won’t be applied to classes that override ToString so it’s safe).
  • Security. If you multicast an authorization aspect to all fields or methods so that some permissions are required, you are guaranteed to never forget to request a permission. With security, an opt-out approach is often safer than opt-in because applying it to an unintended field is preferable to not applying the aspect by mistake.

In some cases, not even multicasting will be precise enough for you. In those cases, you can use aspect providers or compile time validation. It is also possible to multicast aspects to subclasses using multicast inheritance.

Conclusion

We often say that one of the benefits of PostSharp is that it saves you from writing extra repetitive lines of code.

Multicasting is one of the ways that allows you to do that, by factoring out common code and applying it at the class or assembly level.

For more examples and help with multicasting, see our product documentation.