23

I have custom attribute defined like so:

  [AttributeUsage(AttributeTargets.Field)]
  public class EnumDisplayAttribute : Attribute
  {
    public string Description { get; private set; }
    public string Code { get; private set; }

    public EnumDisplayAttribute(string description = null, string code = null)
    {
      Description = description;
      Code = code;
    }
  }

Both constructor parameters are optional.

When using this attribute on a field like so

  public enum TransactionType
  {
    [EnumDisplay(code: "B")] 
    Bill,
    [EnumDisplay(description: null, code: "C")]
    CashReceipt,
  }

I don't see any squigglies in the code editor but I see a vague error without any File Line number of column. The error message is:

error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

Clicking on the error does nothing. That is, you don't get navigated to the error site (obviously, since there is no line number and column).

even if I set up the attribute like so:

[EnumDisplay("This is a Bill")] 

The compiler doesn't like it.

Effectively, I am forced to provide both parameters (named or not) in order to use this attribute as an attribute.

Of course if I use this attribute as a regular class like so:

var enumDisplayAttribute = new EnumDisplayAttribute();
enumDisplayAttribute = new EnumDisplayAttribute(description: "This is a Bill");
enumDisplayAttribute = new EnumDisplayAttribute(code: "B");
enumDisplayAttribute = new EnumDisplayAttribute(description: "This is a Bill", code: "B");
enumDisplayAttribute = new EnumDisplayAttribute("This is a Bill", "B");
enumDisplayAttribute = new EnumDisplayAttribute("This is a Bill");

The compiler will accept any one of the above "styles".

Surely, I'm missing something or my brain is just not working.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
Shiv Kumar
  • 9,599
  • 2
  • 36
  • 38
  • possible duplicate of [Default value for attribute constructor?](http://stackoverflow.com/questions/3436848/default-value-for-attribute-constructor) – Mike Nakis May 29 '13 at 14:26

3 Answers3

31

Optional parameters were added to C# after optional values for attributes already existed in C#. Therefore, for optional attribute parameters, you should fall back to the attribute-specific syntax:

[AttributeUsage(AttributeTargets.Field)]
public class EnumDisplayAttribute : Attribute
{
    public string Description { get; set; }
    public string Code { get; set; }

    public EnumDisplayAttribute()
    {
    }
}

public enum TransactionType
{
    [EnumDisplay(Code = "B")] 
    Bill,
    [EnumDisplay(Description = null, Code = "C")]
    CashReceipt,
}

As you see, the end-result is effectively the same, but instead of using named arguments, you are using named properties (where syntax like [EnumDisplay(Description = null, Code = "C")] is only possible in attribute declarations).

Another way to think of it is that attribute declarations "borrowed" its syntax from method/constructor invocations, but attribute declarations are not in themselves method invocations, so they don't get all the same features as methods.

Kirk Woll
  • 76,112
  • 22
  • 180
  • 195
  • 4
    so it's really a bug of sorts. The compiler needs to sync the way it treats attributes versus other classes. At the very least, the compiler should allow both syntaxes and the error message needs to be clearer indicating line number and column number etc. At least you cleared up the confusion I had. Thanks! – Shiv Kumar Nov 18 '11 at 22:40
  • I agree, I think it's fair to regard this as a "bug of sorts". The lack of line numbers seems inconsistent and obviously unhelpful. (FWIW, ReSharper also fails to recognize this as an error) – Kirk Woll Nov 18 '11 at 22:53
  • FYI In some cases, using the constructor syntax can cause strange compiler errors (depending on the parameter types) that won't list a line number. Using the named parameter syntax seems to be the best way to go for attributes. – TJB Dec 04 '12 at 00:39
  • See also http://stackoverflow.com/questions/8290853/attribute-argument-must-be-a-constant-error-when-using-an-optional-parameter-in The bug with C#-4-style optional parameters happens only when the default value is `null`. That bug should be fixed in the new version (Visual C# 5). – Jeppe Stig Nielsen Apr 08 '13 at 17:46
5

If you do want to push values into your attribute using a constructor (e.g. if some of your attribute's properties are mandatory or to perform some kind of processing on them) you can always go old school and overload the constructor.

For example:

[AttributeUsage(AttributeTargets.Field)]
public class SampleAttribute : Attribute
{
    public string MandatoryProperty { get; private set; }
    public string OptionalProperty { get; private set; }

    // we use an overload here instead of optional parameters because 
    // C# does not currently support optional constructor parameters in attributes
    public SampleAttribute(string mandatoryProperty)
        : this(mandatoryProperty, null)
    {
    }

    public SampleAttribute(string mandatoryProperty, string optionalProperty)
    {
        MandatoryProperty = mandatoryProperty;
        OptionalProperty = optionalProperty;
    }
}
Jonathan Moffatt
  • 13,309
  • 8
  • 51
  • 49
3

Optional parameters are not really optional, the method signature has all arguments in it and attributes are special (existed before optional parameters and have different rules when applied as an attribute (eg consider who calls the attribute constructor)). I imagine however that support will be added in the future.

For now, if you wish to achieve the optional effect try the following:

[AttributeUsage(AttributeTargets.Field)]
public class EnumDisplayAttribute : Attribute
{
  public string Description { get; set; }
  public string Code { get; set; }

}

And apply as so:

[EnumDisplay(Description = null, Code = "C")]
private object _aField;
Rich O'Kelly
  • 41,274
  • 9
  • 83
  • 114