Jon uses a post-build step to add modify generic constraints. His process: ILDASM, then find-and-replace, then ILASM. That’s possible because the requirement is pretty easy.
But here he challenged me:
I've looked at PostSharp and CCI; both look way more complicated than the above.
Sure, PostSharp
would be more complex, but Jon’s ILASM-based solution works only in best cases.
There are plenty of things that you have to keep in mind while building a post-build
step that modifies MSIL. For instance, how will ILASM find referenced assemblies?
What with signed assemblies? Sure,
you can find a solution to everything – after all, PostSharp uses ILASM as a back-end
(contrarily to rumors, it does not use
ILDASM). But, believe me, addressing these side issues will prove more difficult
than the core job itself.
And anyway, why
would it be so complex to implement these rather simple requirements using PostSharp?
PostSharp is designed so that easy jobs get easily done.
I took the challenge
and implemented an add-in adding arbitrary constraints to generic parameters. I
measured time. It took me 30 minutes. NDepend counts 12 lines of code.
It’s based on custom attributes. PostSharp loves custom attributes. I defined a custom attribute AddGenericConstraintAttribute that can be applied to generic parameters. Here is how it’s used:
public static class EnumHelper { public static T[] GetValues<[AddGenericConstraint( typeof(Enum) )] T>() where T : struct { return (T[]) Enum.GetValues( typeof(T) ); } }
The project can
be downloaded from
https://postsharp-user-samples.googlecode.com/files/AddGenericConstraint.zip.
In order to use the plug-in, follow the
documentation.
Here are the
principal steps I followed to create this plug-in:
1.
Create
a new solution AddGenericConstraint.sln.
2.
Create
a new class library project AddGenericConstraint.csproj. This project will be the
public interface of the plug-in.
3.
Create
the class AddGenericConstraintAttribute.cs:
[AttributeUsage( AttributeTargets.GenericParameter )] public sealed class AddGenericConstraintAttribute : Attribute { public AddGenericConstraintAttribute( Type type ) { } }
4.
Create
a new class library project AddGenericConstraint.Impl.csproj. This will contain
the plug-in implementation.
5.
Add
references to PostSharp.Public.dll, PostSharp.Core.dll, AddGenericConstraint.csproj.
6.
Create
a class AddGenericConstraintTask. This is the real implementation class. This is
the hard point.
public class AddGenericConstraintTask : Task { public override bool Execute() { // Get the type AddGenericConstraintAttribute. ITypeSignature addGenericConstraintAttributeType = this.Project.Module.FindType( typeof(AddGenericConstraintAttribute), BindingOptions.OnlyDefinition | BindingOptions.DontThrowException ); if ( addGenericConstraintAttributeType == null ) { // The type is not referenced in the current module. There cannot be a custom attribute // of this type, so we are done. return true; } // Enumerate custom attributes of type AddGenericConstraintAttribute. AnnotationRepositoryTask annotationRepository = AnnotationRepositoryTask.GetTask( this.Project ); IEnumeratorcustomAttributesEnumerator = annotationRepository.GetAnnotationsOfType( addGenericConstraintAttributeType.GetTypeDefinition(), false ); while ( customAttributesEnumerator.MoveNext() ) { // Get the target of the custom attribute. GenericParameterDeclaration target = (GenericParameterDeclaration) customAttributesEnumerator.Current.TargetElement; // Get the value of the parameter of the custom attribute constructor. ITypeSignature constraintType = (ITypeSignature) customAttributesEnumerator.Current.Value. ConstructorArguments[0].Value.Value; // Add a generic constraint. target.Constraints.Add( constraintType ); // Remove the custom attribute. ((CustomAttributeDeclaration) customAttributesEnumerator.Current).Remove(); } return base.Execute(); } }
7.
Add
an XML file AddGenericConstraint.psplugin to the project. This file will describe
your plug-in. In file properties, specify “Copy to Output Directory: Copy Always”.
<?xml version="1.0" encoding="utf-8" ?> <PlugIn xmlns="https://schemas.postsharp.org/1.0/configuration"> <TaskType Name="AddGenericConstraint" Implementation="AddGenericConstraint.Impl.AddGenericConstraintTask, AddGenericConstraint.Impl" Phase="Transform"> <Dependency TaskType="Remove" Position="After"/> <Dependency TaskType="AnnotationRepository"/> </TaskType> </PlugIn>
8.
Go
back to project AddGenericConstraint. Add a reference to PostSharp.Public.dll.
Add the following on the top of the
class AddGenericConstraintAttribute to bind the custom attribute to its implementation:
[RequirePostSharp( "AddGenericConstraint", "AddGenericConstraint" )]
9.
Create
a test project. Add references to PostSharp.Public.dll and AddGenericConstraint.csproj.
In project properties, add directory “..AddGenericConstraint.ImplinDebug” to
reference paths.
10.
You
are done.
What’s the catch?
Maybe that, if you don’t know PostSharp Core and MSIL, it will take you days to
come with the 12 lines of code that form the implementation. But when you got the
knowledge, you are incredibly productive. And all the issues caused by the integration
in the build process are solved for you. Believe me, after 5 years of existence,
there is a huge knowledge behind PostSharp
integration.
Happy PostSharping!
-gael
PS. It took me
longer to write this blog than the implementation itself.