Nullable int in Attribute Constructor or Property

Viewed 27098

So I have a custom attribute, let's call it MyCustomAttribute, which has a constructor like so:

public MyCustomAttribute(int? i)
{
   // Code goes here
}

and declares a property:

public int? DefaultProperty { get; set; }

Now if I wanted to use the property, I'd need to pass in an int or null, well that's what I'd expect.

But this gives a compiler error:

[MyCustomAttribute(1, DefaultProperty = 1)]
public int? MyProperty { get; set; }

and so does this:

[MyCustomAttribute(null,DefaultProperty = null)]
public int? MyProperty { get; set; }

The error is: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type for both the constructor and the property.

Why is this? If I change the constructor to take an int, I can pass in 0, but not null, which sort of defeats the purpose of the value (which can sometimes be null)

6 Answers

I know that this is an old question, but no-one has really answered why Nullable<> is not allowed. This conclusive answer to this lies in the documentation for compiler error CS0655:

Positional and named parameters for an attribute class are limited to the following attribute parameter types:

  • One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, or ushort.
  • The type object.
  • The type System.Type.
  • An enum type, provided that it has public accessibility, and that any types in which it is nested also have public accessibility.
  • Single-dimensional arrays of the preceding types.

This documentation page is for Visual Studio 2008, but I haven't heard of any recent changes in this area.

A guy buys a Ferrari, but some fool set the governor (the contraption that limits the speed of the car) to 30 mph. The guy cannot change the governor to something more reasonable, so he rips it out. Now the Ferrari can go as fast as the engine can muscle.

Microsoft doesn't let you use nullables as custom attribute properties. However, Microsoft does let you use strings, which can be null, as custom attribute properties. So rip out the limitation altogether. Replace your nullable int with a string. Sure, a string is even less restrictive than a nullable int as it can have non-integer values like "bob", but that's the price you pay for Microsoft screwing up custom attributes, a language feature, for an implementation detail in the CLR that should be irrelevant.

Here's my example.

public abstract class Contract : Attribute, IContract
{
    public abstract void Check (Object root, String path, Object valueAtPath);
}

public sealed class DecimalPlacesContract : Contract
{
    public String MinimumMantissaCount
    {
        get
        {
            return minimumMantissaCount?.ToString();
        }

        set
        {
            minimumMantissaCount = value == null ? (int?) null : Int32.Parse(value);
        }
    }

    public String MaximumMantissaCount
    {
        get
        {
            return maximumMantissaCount?.ToString();
        }

        set
        {
            maximumMantissaCount = value == null ? (int?) null : Int32.Parse(value);
        }
    }

    public String MinimumSignificantDigitCount
    {
        get
        {
            return minimumSignificantDigitCount?.ToString();
        }

        set
        {
            minimumSignificantDigitCount = value == null ? (int?) null : Int32.Parse(value);
        }
    }

    public String MaximumSignificantDigitCount
    {
        get
        {
            return maximumSignificantDigitCount?.ToString();
        }

        set
        {
            maximumSignificantDigitCount = value == null ? (int?) null : Int32.Parse(value);
        }
    }

    private int? minimumMantissaCount;
    private int? maximumMantissaCount;
    private int? minimumSignificantDigitCount;
    private int? maximumSignificantDigitCount;



    public override void Check (Object root, String path, Object valueAtPath)
    {
        decimal? value = valueAtPath as decimal?;

        int mantissaCount = DecimalMisc.GetMantissaDigitCount(value ?? 0);
        int significantDigitCount = DecimalMisc.GetSignificantDigitCount(value ?? 0);

        if (value == null ||
            mantissaCount < minimumMantissaCount ||
            mantissaCount > maximumMantissaCount ||
            significantDigitCount < minimumSignificantDigitCount ||
            significantDigitCount > maximumSignificantDigitCount)
        {
            throw new ContractException(this, root, path, valueAtPath);
        }
    }
}

Here's how to use the custom attribute properties.

    private class Dollar
    {
        [DecimalPlacesContract (MinimumMantissaCount = "0", MaximumMantissaCount = "2")]
        public decimal Amount { get; set; }
    }

    private class DollarProper
    {
        [DecimalPlacesContract (MinimumSignificantDigitCount = "2", MaximumSignificantDigitCount = "2")]
        public decimal Amount { get; set; }
    }

Just add quotes around the values. Unspecified properties default to null.

Any improper value like "bob" will cause a FormatException to be thrown when you call GetCustomAttributes or GetCustomAttribute off the Type or PropertyInfo instance. Sure, it would be nice to have the compile-time check of the nullable int, but this is good enough. So rip that governor right out.

Related