Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enum as an parameter on it's own custom attribute constructor

Here I was spending some time to learn more about C#, so I decided to look into custom Attributes, and I find them quite useful when assigned to Enums.

So I wrote a few extension methods for Enum's to retrieve such attributes quite easily, like for example: DemoEnum.Value1.GetAttribute<EnumNote>().

After a while I thought It would be a nice idea if, every custom Attribute would have a reference to the Enum it was assigned to. Not a bad idea I thought, so I went ahead with this:

First I wrote a base class EnumAttribute for custom Attributes, who inherits the System.Attribute class of course. This base class is just a first sketch, and I intent to expand it to specially suit every Type of Enum it will be receiving, but so far this will suffice.

public class EnumAttribute : Attribute
{
    public EnumInfo Enum { get; internal set; }

    public EnumAttribute(Enum Enum)
    {
        this.Enum = new EnumInfo(Enum);
    }

    public class EnumInfo
    {
        private Enum _value;
        private Type _type;
        private FieldInfo _details;

        public Enum Value { get { return _value; } }
        public Type Type { get { return _type; } }
        public FieldInfo Details { get { return _details; } }

        public EnumInfo(Enum value)
        {
        _value = value;
        _type = value.GetType();
        _details = _type.GetField(System.Enum.GetName(_type, value));
        }
    }
}

Now every custom Attribute would have to inherit from this class EnumAttribute. Resulting for example in a class like EnumNote:

public class EnumNote : EnumAttribute
{
    private string _note = string.Empty;

    public string Note { get { return _note; } }

    public EnumNote(Enum Enum, string Note)
        : base(Enum)
    {
        _note = Note;
    }
}

So far all is good, Visual Studio code analysis and compiler report nothing.

But when I define a Enum like for example:

public enum DemoEnum
{
    [EnumNote(DemoEnum.Value1, "Some special note about Enum Value1.")]
    Value1 = 1,

    [EnumNote(DemoEnum.Value2, "Some other special note about Enum Value2.")]
    Value2 = 2
}

And when I try to compile it, VS reports on the first argument of each EnumNote constructor the following:

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

Basically is saying that DemoEnum.Value1 and DemoEnum.Value2 don't hold a constant value. I'm I right?

Any way, this error has me puzzled because this Enum is hard-coded, and as you see the compiler won't even have to assign a value to each enum since I already did it myself.

This of course brings the question, what I'm missing or mis-interpreting, and how should I go about accomplishing the goal of providing a reference to the enum where each EnumNote is being assigned to?

Thank you.

Update:

After a fresh look at it, I understand why VS reports the enums aren't a constant expression, thats because I'm referring something: DemoEnum.Value1 in a point, where he hasn't finished going through the DemoEnum to give a definition to Value1 in the first place. However no ideas on how to go ahead with this idea of mine.

Update 2:

What about refactoring the Enum after it has been compiled, it probably shouldn't give any errors then, unless it can't be applied to enums (can't remember if refactoring works with enums), but if it can be applied it will probably be messy to do and slow right?

Update 3: Reason why the Attributes contain reference to the Enum they were assigned to.

It's a pretty simple reason actually, assume the following scenario. Lets say I have a collection of custom Attributes, and for some reason I require at some point to know to which Enum a given Attribute belongs to.

Instead of writing new code to determine that, why not simply reference the given Enum in the Attribute itself? It's a tiny compromise of memory expended, while in the future it would save valuable time running any required process to determine the given Enum of each Attribute (or even just one).

like image 559
Fábio Antunes Avatar asked Feb 14 '13 04:02

Fábio Antunes


2 Answers

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

Basically is saying that DemoEnum.Value1 and DemoEnum.Value2 don't hold a constant value. I'm I right?

Not quite.

The actual problem seems to be the use of Enum. While it functions mostly as a base class for a conceptual class hierarchy, it appears to be handled with a bit of trickery. For all intents and purposes it's a run-time pseudo-class rather than a true class.

Attributes are processed at compile-time. There is no mechanism for decorating objects with attributes at run-time. As such the introduction of a bit of run-time trickery to handle the Enum pseudo-class appears to be incompatible with the way Attributes function.

Try this:

public class EnumTest : Attribute
{
    public int Value;
    public object Obj;
}

public enum DemoEnum
{
    [EnumTest(Value = (int)DemoEnum.Value1, Obj = DemoEnum.Value1)]
    Value1 = 1,

    [EnumTest(Value = (int)DemoEnum.Value2)]
    Value2 = 2
}

This compiles fine (.NET 4.0 in VS2010), and the Attribute properties are set correctly. I can examine the Obj property of the Attribute and get the sort of data I'd expect.

<opinion>
That said... not quite sure how useful this would be. You already have to have an instance of the value you're decorating, so having the value itself stored in the Attribute seems a little redundant.
</opinion>

Edit: a possible solution...

After letting this percolate a bit (I do some of my best thinking while asleep), I have realized that since the problem is with the actual parameter types (as mentioned in the answer to this question) we can work around the issue with a bit of boxing.

public class EnumTest : Attribute
{
    public Enum enum;

    public EnumTest(object e)
    {
        enum = e as Enum;
    }
}

This way you are initializing the Attribute from an object, which C# and the CLR appear to be quite comfortable doing. You lose the type checking inherent in initializing with an Enum, but I believe that it achieves the stated objective.

like image 160
Corey Avatar answered Sep 22 '22 15:09

Corey


You cannot pass to attribute constructor non-constant in compile time value. It is restriction to attributes. So you can only

[EnumNote(1,"Some special note")]
Value1=1,

But your purpose broke the idea of attributes that is mixing aspect-oriented programming in c#. So do not do it with attributes.

About attributes and aspect-oriented programming you can read this article

like image 43
Kirill Bestemyanov Avatar answered Sep 25 '22 15:09

Kirill Bestemyanov