Reference
You can develop an Composition aspect by deriving
the custom attribute CompositionAspect or by
implementing the interface ICompositionAspect.
Overview
The Composition aspect injects new interfaces into
an existing type and defers the implementation of these interfaces
to another object that implements them. This object is said to be
composed into the target type.
Type composition is often considered as a better alternative to multiple inheritance, since complex types are composed of many easiest ones.
More importantly, the Composition aspect can be
used to automatically implement interfaces whose implementation is
generally plumbing code, for instance the
INotifyPropertyChanged interface. This aspect is very
often one of the components of compound aspects.
This aspect has both compile-time semantics and runtime semantics.
At compile-time, methods GetPublicInterface
and GetProtectedInterfaces
are invoked. The aspect should return the set of interfaces that
needs to be implemented. We will talk later about protected
interfaces.
At runtime, just after the base constructor of the aspected
object has been invoked, the CreateImplementationObject
method of the aspect is invoked. This method should provide an
implementation of composed interfaces.
Sample
The following aspect injects the IList interface to
the class to which it is apply. The implementation of this
interface is delegated to the type ArrayList.
[Serializable]
public class ComposeListAttribute : CompositionAspect
{
public override Type GetPublicInterface( Type type )
{
return typeof(IType);
}
public override object CreateImplementationObject(
InstanceBoundLaosEventArgs eventArgs )
{
return new ArrayList();
}
}
[ComposeList]
class ComposedList { }
Casting objects to their composed interfaces
Since the interface is injected after compilation, you cannot
use the classic C# or Visual Basic casting operator unless you
first downcast the instance to System.Object:
ComposedList composedList = new ComposedList(); IList list = (IList) (object) composedList;
The problem of this construct is that it is unsafe (no compile-time error is emitted if the object does not implement the interface) and it has a useless performance overhead.
For this use case, PostSharp provides the casting method Post.Cast<TSource,TTarget> which is both safe and efficient because it is checked and transformed at compile time.
ComposedList composedList = new ComposedList(); IList list = Post.Cast<ComposedList,IList> composedList;
Accessing the implementation object
It is often useful to get directly the object implementing a
composed interface. For instance, when you implement the
INotifyPropertyChanged interface using a composition
aspect, we need to raise the PropertyChanged event
from property setters of the target type. But since we cannot raise
this event using the semantics of the
INotifyPropertyChanged interface, we need direct
access to the implementation object.
If you need access to the implementation object, you have to ask
the Composition aspect to generate an implementation
accessor. This is done by overriding the GetOptions
method and setting the flag
GenerateImplementationAccessor. This tells PostSharp
to implement another interface in the target type, IComposed<T>, where
T is the type of the public interface. The GetImplementation
method of this interface gives you access to the implementation
object.
Thanks to the SetImplementation
method, you can change the implementation object on the fly. This
feature is one of the biggest advantages of type composition over
traditional type inheritance.
There would be a security break, however, if any type had the
right to access the implementation. For this reason, implementation
accessors require so-called instance credentials (an object
of type InstanceCredentials).
Only code that knows the credentials of an instance can access the
implementation of a composed interface. Next section will explain
how to get these credentials. Before that, let us complete this
topic and show how to practically access the implementation object
to invoke a method that is not a part of the exposed interface
(here the Sort method).
[ComposeList]
class ComposedList
{
public void Sort()
{
InstanceCredentials credentials = InstanceCredentials.GetCredentials(this);
IComposed<IList> composed = Post.Cast<ComposedList,IList>(this);
ArrayList list = (ArrayList) composed.GetImplementation(credentials);
list.Sort();
}
}
Getting instance credentials
The objective behind instance credentials is to enforce good
encapsulation by preventing external code from accessing
implementation details. Only code that is a part of the family of
an instance can access implementation details. In that sense,
instance credentials achieve a similar goal than the
protected C# keyword. The difference is that the
notion of 'family' is enlarged: any aspect active on a class is
considered to be a part of the family of that class.
A typical example is an aspect implementing
INotifyPropertyChanged, where a property setter aspect
needs to access the implementation of a composed interface so that
it can raise the PropertyChanged event.
There are two ways to get the credentials of an instance:
- Aspect Handlers receive the instance credentials on the InstanceCredentials property of the event arguments object.
-
Other code can call the method InstanceCredentials.GetCredentials<T>(T o). Calls to
this method are analyzed at compile-time so that only types derived
from
Tare allowed to invoke the method.
Note that instance credentials are intended to enforce good reason, but should not be used to enforce security requirements. Instance credentials are actually a random 32-bit value and are relatively easy and cheap to break, even if the caller assembly is partially trusted.
Protected interfaces
You can inject protected interfaces to an existing type by
overriding the method GetProtectedInterfaces()
of the aspect.
When a type exposes a protected interface, one cannot cast that type to the protected interface without providing instance credentials.
Instead of exposing the protected interface directly, the type
implements the interface IProtectedInterface<T>,
where T is the protected interface in itself. One can
get a reference to the interface (which is actually the
implementation object) by calling the GetInterface
method. This method requires the instance credentials.
Exposing a protected interface is a cleaner alternative to
exposing the implementation object through the
IComposed interface, even if the same implementation
object is finally returned.
When the composed interface is already implemented
The default behavior of the Composition aspect is
to emit an error when the aspect is applied on a type that already
implements, even indirectly, the interface to be composed.
Sometimes, it is preferable to silently skip the aspect in this
case. You can force this behavior by overriding the GetOptions
method and setting the flag
IgnoreIfAlreadyImplemented.