Generic constraints for enums and delegates

by Gael Fraiteur on 11 Sep 2009

Yesterday, C# MVP Job Skeet blogged about a  way to add generic constraints for enums and delegates. He noticed that, while the C# compiler forbids such constructions, it is perfectly possibly in MSIL. And he is fully right with this.

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 );

        IEnumerator customAttributesEnumerator =
            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.