Beware exceptions in attribute constructors

If you’d like to have some really wacky bugs, be sure to do something like this:

public class BlowupAttribute : Attribute
{
    public BlowupAttribute(int time)
    {
        if (time <= 0)
            throw new ArgumentOutOfRangeException("time", time, "Must be greater than zero.");
    }
}

Attributes are a little different than other classes, as you’re never really in control of when the constructor gets called.  Things get especially weird in unit tests, where test runners do quite a bit of reflection that triggers the constructors of attributes.  This test fails:

[Blowup(-1)]
public void Asplode()
{
}

[Test]
public void Fails_anyway()
{
    true.ShouldBeTrue();
}

Simply because another member in the class has a bad value in the attribute.  Unfortunately, the stack trace gives absolutely zero hint on where the exception occurred:

TestCase 'M:AttributeExceptions.Blarg.Fails_anyway'
failed: Must be greater than zero.
Parameter name: time
Actual value was -1.
    System.ArgumentOutOfRangeException: Must be greater than zero.
    Parameter name: time
    Actual value was -1.
    C:devMSTestSpecMSTestSpec.TestsAttributeExceptions.cs(13,0): at AttributeExceptions.BlowupAttribute..ctor(Int32 time)
    at System.Reflection.CustomAttribute._CreateCaObject(Void* pModule, Void* pCtor, Byte** ppBlob, Byte* pEndBlob, Int32* pcNamedArgs)
    at System.Reflection.CustomAttribute.CreateCaObject(Module module, RuntimeMethodHandle ctor, IntPtr& blob, IntPtr blobEnd, Int32& namedArgs)
    at System.Reflection.CustomAttribute.GetCustomAttributes(Module decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType, Boolean mustBeInheritable, IList derivedAttributes)
    at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeMethodInfo method, RuntimeType caType, Boolean inherit)
    at NUnit.Core.Reflect.HasAttribute(MemberInfo member, String attrName, Boolean inherit)
    at NUnit.Core.Reflect.GetMethodWithAttribute(Type fixtureType, String attributeName, BindingFlags bindingFlags, Boolean inherit)
    at NUnit.Core.NUnitTestFixture..ctor(Type fixtureType)
    at NUnit.Core.Builders.NUnitTestFixtureBuilder.MakeSuite(Type type)
    at NUnit.Core.Builders.AbstractFixtureBuilder.BuildFrom(Type type)
    at NUnit.Core.Extensibility.SuiteBuilderCollection.BuildFrom(Type type)
    at NUnit.Core.Builders.TestAssemblyBuilder.GetFixtures(Assembly assembly, String ns)
    at NUnit.Core.Builders.TestAssemblyBuilder.Build(String assemblyName, Boolean autoSuites)
    at NUnit.Core.Builders.TestAssemblyBuilder.Build(String assemblyName, String testName, Boolean autoSuites)
    at NUnit.Core.TestSuiteBuilder.BuildSingleAssembly(TestPackage package)
    at NUnit.Core.TestSuiteBuilder.Build(TestPackage package)
    at NUnit.AddInRunner.NUnitTestRunner.run(ITestListener testListener, Assembly assembly, ITestFilter filter)
    at NUnit.AddInRunner.NUnitTestRunner.runMethod(ITestListener testListener, Assembly assembly, MethodInfo method)
    at NUnit.AddInRunner.NUnitTestRunner.RunMember(ITestListener testListener, Assembly assembly, MemberInfo member)
    at TestDriven.TestRunner.AdaptorTestRunner.Run(ITestListener testListener, ITraceListener traceListener, String assemblyPath, String testPath)
    at TestDriven.TestRunner.ThreadTestRunner.Runner.Run()

Nowhere do we see where the original attribute was declared, that information is lost.  Attributes are meant for metadata, and exceptions can really screw things up.  To be safe, avoid throwing exceptions in an attribute constructor.  Also, be very careful of any complex operations done in the constructor.  If you’re doing anything more than merely capturing parameters passed in, you’re doing too much.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in C#. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Captain Obvious

    “Nowhere do we see where the original attribute was declared, that information is lost.”

    … AttributeExceptions.cs(13,0): at AttributeExceptions.BlowupAttribute..ctor(Int32 time)

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Captain Obvious

    That’s where the attribute class is declared, but not the usage. I have no information on which attribute usage caused the exception.

    More obvious now? :)