The PostSharp.Samples.Composition
project demonstrates the use of PostSharp Laos to compose objects.
What is object composition? Say you want to implement a
collection (ICollection
)
based on an ArrayList
, but you want to hide ArrayList
and don't want to implement each method manually. PostSharp can do it
for you. Look at the following code sample:
[SimpleComposition( typeof(ArrayList), typeof(ICollection))]
class SampleCollection
{
// Optionally add some methods here.
}
After processing by PostSharp, the SampleCollection
class will implement the ICollection
method
based on the ArrayList
implementation.
The functionality of composing an object into another is provided by the Composition Aspect. All we have to do in our sample is, conceptually, to give parameters to this Composition Aspects. That is, we have to tell which interface has to be composed, and which implementation object has to be used. Both types are the arguments of our custom attribute.
So what we have to do is finally really easy: we create a class SimpleCompositionAttribute
derived from CompositionAspect
, we add a constructor with two parameters, and we override the GetPublicInterface
and CreateImplementationObject
methods. Because we need to create an instance of the implementation class (ArrayList
in the sample use case here above), we have to require the implementation class to have a default constructor.
A minor issue appears: how to serialize both interface and implementation type names? We cannot simply serialize the Type
instance, because it is not safely serializable. So we will store the type names in the class.
[Serializable]
public sealed class SimpleCompositionAttribute : CompositionAspect
{
// Name of the type of the composed object.
string implementationTypeName;
// Name of the exposed interface.
string interfaceTypeName;
public SimpleCompositionAttribute(Type interfaceType, Type implementationType)
{
if (implementationType != null)
{
this.implementationTypeName = implementationType.FullName;
}
if (interfaceType != null)
{
this.interfaceTypeName = interfaceType.FullName;
}
}
public override object CreateImplementationObject(InstanceBoundLaosEventArgs eventArgs)
{
return Activator.CreateInstance( GenericArg.Map( this.ImplementationType, eventArgs.Instance.GetType().GetGenericArguments(), null));
}
public override Type GetPublicInterface(Type containerType)
{
return GenericArg.Map( this.InterfaceType, containerType.GetGenericArguments(), null );
}
}
If you have been attentive, you have noticed the call to GenericArg.Map
. What is it?
Well, we have been a little more ambitious. We want to be able to compose generic classes. For instance, we want to do able to do this:
[SimpleComposition(typeof(IList<GenericTypeArg0>), typeof(List<GenericTypeArg0>))]
class SimpleList<T>
{
}
Note that we have use the type GenericTypeArg0
. This is a 'magic type' meaning that you refer to the first generic type argument in the current context, that is, T
in our example.
The purpose of the GenericArg.Map
method is precisely to resolve this 'magic type'.
It smells like a hack, but in fact it is much cleaner than its appear. We give a general mechanism to provide unbound generic parameters and a method to bind them to their context.
We've already given two examples of how to define a composed class. But how to use these classes?
Since the new interface is added after compilation, you will
not see the methods of this interface using IntelliSense and your
program won't even compile if you reference them. Instead, you should
explicitly cast the class. And to avoid your compiler complaining that
the class cannot be casted to this interface, you first have to cast to
object
.
IList<string> list = (IList<string>)(object)new SimpleList<string>();
list.Add("dog");
list.Add("cat");
list.Add("cow");