169

It is really unbelievable but real. This code will not work:

[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field)]
public class Range : Attribute
{
    public decimal Max { get; set; }
    public decimal Min { get; set; }
}

public class Item
{
    [Range(Min=0m,Max=1000m)]  //compile error:'Min' is not a valid named attribute argument because it is not a valid attribute parameter type 
    public decimal Total { get; set; }  
}

While this works:

[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field)]
public class Range : Attribute
{
    public double Max { get; set; }
    public double Min { get; set; }
}

public class Item
{
    [Range(Min=0d,Max=1000d)]
    public decimal Total { get; set; }  
}

Who can tell me why double is OK while decimal is not.

Cheng Chen
  • 42,509
  • 16
  • 113
  • 174
  • 1
    possible duplicate of [use decimal values as attribute params in c#?](http://stackoverflow.com/questions/507528/use-decimal-values-as-attribute-params-in-c) – nawfal Jun 10 '13 at 22:46

3 Answers3

171

This is a CLR restriction. Only primitive constants or arrays of primitives can be used as attribute parameters. The reason why is that an attribute must be encoded entirely in metadata. This is different than a method body which is coded in IL. Using MetaData only severely restricts the scope of values that can be used. In the current version of the CLR, metadata values are limited to primitives, null, types and arrays of primitives (may have missed a minor one).

Taken from this answer by JaredPar.

Decimals while a basic type are not a primitive type and hence cannot be represented in metadata which prevents it from being an attribute parameter.

Community
  • 1
  • 1
djdd87
  • 67,346
  • 27
  • 156
  • 195
  • 41
    Why decimals are not considered primitive types in the CLR? – koumides Feb 01 '12 at 16:40
  • 11
    @koumides i believe the answer is the type is too large to express in a single CPU register as it is 128bit – Chris Marisic May 02 '16 at 19:05
  • 4
    OK so why are strings allowed as attribute properties? I suppose it comes under the 'array of primitives' category but it is heap allocated (reference type)... – Steztric Nov 11 '16 at 08:29
  • Because strings are reference types which are handled completely different. – Carsten Schütte Dec 14 '16 at 08:34
  • `System.Enum` is not supported as well. – Søren Jan 14 '19 at 13:31
  • 2
    @Soren this is not true, `Enum` are supported. I currently have 2 custom attributes one with 2 enums and the others with an array of enum. – Franck May 27 '19 at 16:16
  • decimal is a struct, so rather primitve. But the parsing and calculations are implemented by the runtime lib - not the compiler. hence there are no compile time evaluations and validations - which also then restricts attribute usage. Still one might ask why. Why does the compiler not evaluate at compile time. But this is apparently the cut they've made --- somewhere. – Robetto Jan 07 '21 at 12:24
  • You can use Enums, but it needs to be an Array of Enums over a List of Enums if you need to take multiple. – Greg Sep 09 '21 at 10:42
78

From the specs:

The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:

  • One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
  • The type object.
  • The type System.Type.
  • An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility (Attribute specification).
  • Single-dimensional arrays of the above types.
Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198
Kobi
  • 135,331
  • 41
  • 252
  • 292
  • 10
    Correct, but note that you're quoting an old version of the spec. In C# versions 3.0, 4.0, and 5.0, it is stated that it can also have type `sbyte`, `ushort`, `uint`, `ulong`. And that seems to work all right. But still `decimal` is not allowed :-( – Jeppe Stig Nielsen Sep 19 '12 at 10:56
  • 1
    @JeppeStigNielsen I've updated the spec link and quote – Ohad Schneider Nov 25 '17 at 14:54
  • 8
    Nullable primitives are also NOT supported. – KTCO Feb 17 '18 at 18:02
  • I used this syntax to pass a an array of string to constructro: params string[] When calling the constructor you should separate string by comma – hossein hashemian Mar 02 '22 at 08:07
4

The answer to this problem is to use strings, which are allowed as attributes despite not being an atomic type. Don't use doubles as rounding will make the results less accurate.

public String MinimumValue
{
    get
    {
        return minimumValueDecimal.ToString();
    }

    set
    {
        minimumValueDecimal = Decimal.Parse(value);
    }
}

private decimal minimumValueDecimal;
Daniel Barbalace
  • 1,197
  • 1
  • 17
  • 10