2

I am confused, because in C#, a decimal literal such as 1.0m can be used as a constant field, but not as an argument to an attribute.

[Foo(1.0m)] // Error
class C
{
    const decimal constD1 = 1.0m;
}

In a thread at MSDN, James Curran replied:

"1000M" is merely shorthand for "new Decimal(1000)", which involves a method call, which means it's not considered a constant. Just because the compile lets you pretend it's a constant most of the time, doesn't mean you can all of the time.

But if it is a shorthand for a constructor call, you should be able to do this:

class C
{
    const decimal constD2 = new decimal(1.0); // Error
}

If I understand Jon Skeet's reply to a similar question correctly, it is impossible to pass a decimal to an attribute, which means, I can never successfully do something like this:

class FooAttribute : Attribute
{
    public FooAttribute(object obj)
    {
        if (obj is decimal)
        {
            // Doomed to be dead code?
        }
    }
}

Also see this question. By the way, ReSharper does not show a compiler error for a decimal constant/literal in an attribute value, which is a known issue.

Here is a full example:

namespace ClassLibrary1
{
    [Foo("")]
    [Foo(new string(new char[] { }))] // Error 1
    [Foo(1.0m)] // Error 1 (no error in ReSharper)
    [Foo(new decimal(1.0))] // Error 1
    class C
    {
        static readonly string s1 = "";
        static readonly string s2 = new string(new char[] {});
        static readonly decimal d1 = 1.0m;
        static readonly decimal d2 = new decimal(1.0);

        const string constS1 = "";
        const string constS2 = new string(new char[] {}); // Error 2
        const decimal constD1 = 1.0m;
        const decimal constD2 = new decimal(1.0); // Error 2

        // Error 1:
        // An attribute argument must be a constant expression, typeof expression or 
        // array creation expression of an attribute parameter type

        // Error 2:
        // The expression being assigned to (...) must be constant

    }

    [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
    class FooAttribute : Attribute
    {
        public FooAttribute(object obj)
        {
            if (obj is decimal)
            {
                // Doomed to be dead code?
            }
        }
    }
}

So, what is this thing "1.0m" actually? And how can I work around the fact that I cannot use it as an argument to an attribute? The only solution I can think of is to sacrify type safety and do this if dealing with a custom attribute:

[Bar("1.0")]
class C
{
}

class BarAttribute : Attribute
{
    public BarAttribute(string decimalAsString)
    {
        decimal d = Convert.ToDecimal(decimalAsString);
        ...
    }
}

When dealing with NUnit's TestCaseAttribute, I would do something similar. According to NUnit docs, NUnit converts the type automatically.

[TestCase("1.0")]
public void Test(decimal d)
{
    ...
}
Community
  • 1
  • 1
Marco Eckstein
  • 4,448
  • 4
  • 37
  • 48
  • 4
    It's not entirely clear what new information you're looking for. `1.0m` is a decimal literal, which is a constant expression as far as the C# compiler is concerned, but cannot be represented as a constant value for the CLR, hence you can't specify it in an attribute argument. A string representation is about as close as you can get, basically. – Jon Skeet Jul 25 '14 at 11:41
  • Yes, admittedly, it was hard to put my confusion into a question form at all. An implicit question of mine has been whether I missed something or misunderstood something. I guess you have already answered that, as you have not pointed out mistakes. You also answered my question about what the best workaround is. I still do not get the "C# compiler constant yes, CLR constant no"-thing beyond a "That's how it is, just remeber it"-level. And I still wonder why you cannot "const decimal constD2 = new decimal(1.0)" if "1.0m" is really just a shorthand. – Marco Eckstein Jul 25 '14 at 11:59
  • 1
    It's not really just a shorthand for a constructor, at the language level. The best thing to do is compile some code which has `const decimal x = 1.0m;` and then see what the IL looks like... – Jon Skeet Jul 25 '14 at 12:09
  • @JonSkeet: C# allow `Decimal` values to be specified as default values for parameters. By my understanding, default parameter values are attributes; would that not imply that it must be possible to encode encode `Decimal` values within CIL (by specifying the type and sequence of bytes required to represent the value in that type)? – supercat Aug 06 '14 at 22:34
  • @supercat: Good question. It looks like it encodes it as a separate attribute (`DecimalConstantAttribute`). There are other similar constant attributes available, e.g. for `DateTime`. They even show up under reflection for `ParameterInfo.DefaultValue` - so the framework has some significant knowledge about that. Just not enough to use it for attribute constructors, apparently. Shame :( – Jon Skeet Aug 06 '14 at 22:42

0 Answers0