17

Can anyone explain why this code works:

public class AdministratorSettingValidationAttribute : Attribute
{
    public AdministratorSettingValidationAttribute(AdministratorSettingDataType administratorSettingDataType)
    {
        DataType = administratorSettingDataType;
    }

    public AdministratorSettingValidationAttribute(AdministratorSettingDataType administratorSettingDataType, Type enumerationType)
    {
        DataType = administratorSettingDataType;
        EnumerationType = enumerationType;
    }
}

...but refactoring it to use an optional parameter instead:

    public AdministratorSettingValidationAttribute(AdministratorSettingDataType administratorSettingDataType, Type enumerationType = null)
    {
        DataType = administratorSettingDataType;
        EnumerationType = enumerationType;
    }

...causes a compile time error: "An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type".

Jonathan Moffatt
  • 13,309
  • 8
  • 51
  • 49

2 Answers2

19

UPDATE

The bug was reported in July of last year and is already fixed. The fix will appear in the next version of C#. See this Connect feedback item for details:

http://connect.microsoft.com/VisualStudio/feedback/details/574497/optional-parameter-of-type-string-in-a-attribute-constructor-cannot-be-null


That's clearly a compiler bug. Thanks for bringing it to my attention.

What is supposed to happen here is the compiler is supposed to realize that the optional value expression is implicitly converted to the formal parameter type, and then treat the expression as a constant expression of that type. What it is actually doing is treating the expression as the typeless null literal, which is wrong.

You can work around the bug by turning the constant into an explicitly typed one:

public AdministratorSettingValidationAttribute(AdministratorSettingDataType administratorSettingDataType, Type enumerationType = (Type)null) 

The fix is probably straightforward but I cannot promise that the fix will be in the next version of C#; I'm not sure what the schedule is like for taking non-critical bug fixes at this point.

Thanks again, and apologies for the inconvenience.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 2
    As noted in my comment on the above answer, Mono handles the code just fine, so it's specific to the Visual C# compiler. – BoltClock Nov 28 '11 at 18:00
  • 1
    With Visual Studio 2010, if I have "string myString = (string)null" it still doesn't work (even with the explicit casting). Also the link gives me a page not found. – user276648 Dec 06 '12 at 02:43
  • @user276648 That fix seems to work only when the attribute is used in the same assembly (where the attribute class is defined). If the attribute is defined in one assembly which is compiled first, and the attribute is the applied to a code element in another assembly (and the other assembly references the binary from the first compilation), then the fix doesn't work. You can get around it if you use the empty string `""` as the default value for the optional string parameter. – Jeppe Stig Nielsen Apr 08 '13 at 17:39
  • `string myString = (string) null` does not work for me. Neither does `private const string NullString = null` and using that. I added an overloaded constructor that calls `this(val1, null)`. – ta.speot.is Sep 10 '13 at 06:06
  • This bug is still present in MSVS2013, but with nullable types http://pastebin.com/Y9iCHcT0 Strings is OK. – Viacheslav Ivanov Nov 08 '13 at 07:16
5

This smells like a compiler bug to me. Attribute classes are 'special' classes in a way that they can be used as meta data. The C# compiler allows you to use them differently then normal classes and therefore we can assume that a (partial) custom implementation for compiling usage of attribute classes exists in the C# compiler. (Can anyone test this on mono?)

That said, i did some tests and found that only when using the constructor of an attribute which specifies the default value of a parameter as null without defining a custom value of that attribute, the compiler gives us an error. My test code:

class TestAttribute : Attribute
{
    public TestAttribute(object test = null) { }
    //public TestAttribute(int test = 0) { } 

    public void TestMethod(object test = null) { }
}

class TestClass
{
    public TestClass(object test  = null) { }
}

[Test()] // crashes
//[Test()] // Works when using the constructor taking an int
//[Test(null)] // works
class Program
{
    static void Main(string[] args)
    {
        TestClass t = new TestClass(); // works

        TestAttribute a = typeof(Program).GetCustomAttributes(typeof(TestAttribute), false).Cast<TestAttribute>().First();

        a.TestMethod(); // works
    }
}

(Tested with VS 2010 under .NET 4.0, can anyone test this with mono?)

Note that Attributes already allow you to address properties as if they were optional, so you could make your optional parameter a property (if not already, and remove it from the constructor. This still allows you to write [Test(null, MyProperty = null)]

Polity
  • 14,734
  • 2
  • 40
  • 40
  • Wow... Could it possibly mean that if optional arguments are used at invokation, some code is generated by the compiler to somehow calculate the values of omitted parameters, and this conflicts with the constant value requirement? This could explain the issue, although there should be a more understandable error message, I think. – Pavel Gatilov Nov 28 '11 at 04:31
  • @PavelGatilov - No it can't. First observation is that its perfectly fine to use a default value other than null within the constructor. Second obervation is that an attribute's constructor will be executed just like normal code, each time you access it (Type.GetCustomAttributes(...)). I think we need to wake Eric Lippert up for this one – Polity Nov 28 '11 at 05:15
  • @PavelGatilov - When using an attribute. The compiler will compile the attribute usage as metadata for the location on which you specify the attribute. In here, there is a field called ctor args. When using default parameters and not specifying the parameters when using the attribute, the field will contain 'nothing' meaning that JIT will determine the default value for us. – Polity Nov 28 '11 at 05:27
  • 1
    Tested with Mono 2.10 on Windows, compiles and runs without a hitch. – BoltClock Nov 28 '11 at 17:56