23

I have defined the following enum:

public enum DeviceType
{
    [Description("Set Top Box")]
    Stb = 1,
    Panel = 2,
    Monitor = 3,
    [Description("Wireless Keyboard")]
    WirelessKeyboard = 4
}

I'm utilising the Description attribute to allow me to pull out a more user-readable version of the enum to display in the UI. I get the description using the following code:

var fieldInfo = DeviceType.Stb.GetType().GetField(DeviceType.Stb.ToString());

var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

var description = (attributes.Length > 0 ? attributes[0].Description : DeviceType.Stb.ToString());

The above code will give me: description = "Set Top Box". If there is no Description attribute set, it will give me the string value of the enum.

I now want to add a second/custom attribute to each of the enums (called 'Value' for examples sake). eg:

public enum DeviceType
{
    [Description("Set Top Box")]
    [Value("19.95")]
    Stb = 1,
    [Value("99")]
    Panel = 2,
    [Value("199.99")]
    Monitor = 3,
    [Description("Wireless Keyboard")]
    [Value("20")]
    WirelessKeyboard = 4
}

I will need to pull out the new Value attribute much the same way I currently do with the Description attribute.

Is it possible to extend the existing Description attribute to somehow include the new Value attribute, or is it best to create the new attribute separately?

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
sfinks_29
  • 876
  • 4
  • 20
  • 30
  • 4
    This is XY problem. What do you want to achieve ultimately? – Dennis Oct 02 '15 at 06:43
  • Can you give an example as to how the result should look like with the included value attribute? (it is not 100% clear what you exactly want) – Thomas Oct 02 '15 at 06:45
  • 2
    You can extend or create a new one ,that's not a big problem. But I would also say that when you need more and more attributes on an enum, you should maybe think about creating a class instead.. – Raphaël Althaus Oct 02 '15 at 06:45
  • 2
    I appears that you really want `DeviceType` to be a `class` and not just an `enum`. classes can have multiple properties. – Richard Schneider Oct 02 '15 at 06:46
  • I know it's an example, but why would have a `Value` attribute which takes a string representation of a `double`? – Wai Ha Lee Oct 02 '15 at 06:48
  • I'm basically using the enums as a lookup 'table'. So the Stb enum has a (human readable) description of 'Set Top Box' and a value of '19.95'. Descriptions/Values are constant, so once defined, won't change. The use of enums here might not be the right way to go.. – sfinks_29 Oct 02 '15 at 06:50
  • In Java enums can have fields; does C# not support this? – user253751 Oct 02 '15 at 11:18

4 Answers4

21

Create a new attribute seperately called DeviceInformation...

[AttributeUsage(AttributeTargets.All)]
public class DeviceInformationAttribute : DescriptionAttribute
{
    public DeviceInformationAttribute(string description, string value)
    {
        this.Description = description;
        this.Value = value;
    }

    public string Description { get; set; }
    public string Value { get; set; }
}

You can also use the extension method to retrieve the value of any attribute

static void Main(string[] args)
{
    var info = DeviceType.Stb.GetAttribute<DeviceInformationAttribute>();
    Console.WriteLine("Description: {0}\nValue:{1}",info.Description, info.Value);

}

public static class Extensions
{
    public static TAttribute GetAttribute<TAttribute>(this Enum enumValue)
            where TAttribute : Attribute
    {
        return enumValue.GetType()
                        .GetMember(enumValue.ToString())
                        .First()
                        .GetCustomAttribute<TAttribute>();
    }
}

public enum DeviceType
{
    [DeviceInformation("foobar", "100")]
    Stb = 1,
}

Edit

In response to the comment

@Aydin Adn I do love the use of the extension method, very nice! Do you have a solution for the case of DeviceType.Panel which does not have a description, but needs the Value attribute? (see comments on Patrick's answer)

 [AttributeUsage(AttributeTargets.All)]
public class DeviceInformationAttribute : Attribute
{
    public DeviceInformationAttribute(string description)
    {
        this.Description = description;
    }

    public DeviceInformationAttribute(decimal value)
    {
        this.Description = string.Empty;
        this.Value = value;
    }

    public DeviceInformationAttribute(string description, decimal value)
    {
        this.Description = description;
        this.Value = value;
    }

  
    public string Description { get; set; }
    public decimal Value { get; set; }
}
Community
  • 1
  • 1
Aydin
  • 15,016
  • 4
  • 32
  • 42
  • 1
    You should really derive the existing `DescriptionAttribute` since WPF uses it in its converters for example. – Patrick Hofman Oct 02 '15 at 06:55
  • Interesting to know, I'm not a WPF developer so wasn't aware of its usage. Thanks for the insight! – Aydin Oct 02 '15 at 06:58
  • Don't forget to call the base class constructor and remove the `Description` property. – Patrick Hofman Oct 02 '15 at 07:00
  • 1
    It's basically turned into the same answer as the one you've provided, I'll be removing this post as it adds no more value than yours but consider adding the extension method I've listed to yours because you can reuse it across the board – Aydin Oct 02 '15 at 07:04
  • Is that your own extension method? – Patrick Hofman Oct 02 '15 at 07:05
  • 1
    Yes: http://stackoverflow.com/questions/13099834/how-to-get-the-display-name-attribute-of-an-enum-member-via-mvc-razor-code/25109103#25109103 – Aydin Oct 02 '15 at 07:06
  • 2
    Agree it is a good one, and up-voted. Why not leave it there and leave this question as-is? Just thinking. – Patrick Hofman Oct 02 '15 at 07:07
  • 1
    @Aydin Adn I do love the use of the extension method, very nice! Do you have a solution for the case of DeviceType.Panel which does not have a description, but needs the Value attribute? (see comments on Patrick's answer) – sfinks_29 Oct 02 '15 at 07:12
  • 1
    Sure, change the value type of the property `Value` from a `string` to a `decimal` type, add in those extra parameters – Aydin Oct 02 '15 at 07:17
  • That works perfectly in this example, because `Value` is actually a `decimal` type (although in my example I had it represented as a `string`, for examples sake). I'm just wondering what would happen in the situation where both `Description` and `Value` were `strings` (or even if that is a situation that would ever be required..) – sfinks_29 Oct 02 '15 at 07:36
  • @AydinAdn I have accepted this as the answer due to the use of the extension method, which is a great addition to my existing code. – sfinks_29 Oct 02 '15 at 07:59
  • @sfinks_29 Thank you, glad I was able to help :) – Aydin Oct 02 '15 at 12:49
13

Yes, that is fairly easy to do. Just derive the existing DescriptionAttribute class:

[AttributeUsageAttribute(AttributeTargets.All)]
public class DescriptionWithValueAttribute : DescriptionAttribute
{
    public DescriptionWithValueAttribute(string description, string value) : base(description)
    {
        this.Value = value;
    }

    public string Value { get; private set; }
}

Then you can use it like this:

public enum DeviceType
{
    [DescriptionWithValue("Set Top Box", "19.95")]
    Stb = 1,
}

Your code to retrieve the attributes will remain almost the same, just replace the type names.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • How does this work in the case of, referring back to my original post, DeviceType.Panel which does not have a description, but needs the Value attribute? – sfinks_29 Oct 02 '15 at 06:57
  • You can always create a separate enum if you want to split the logic off. Just don't derive from `DescriptionAttribute` then, just `Attribute`. – Patrick Hofman Oct 02 '15 at 06:58
  • Using your code, I was able to get the Value attribute by modifying my original code used to get the Description attribute: var attributes = (DescriptionWithValueAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionWithValueAttribute), false); Having a separate enum would mean having multiple variations of this code? – sfinks_29 Oct 02 '15 at 07:02
  • 1
    Indeed, which isn't really nice, but possible if it suits your use case. – Patrick Hofman Oct 02 '15 at 07:03
3

why not doing it in one class. It is a bit more work in the beginning but:

  • a lot easier to extend with additional values
  • can use helper functions to create common elements (see Simple())
  • more efficient at runtime because no reflection to get values
  • Binding in Wpf easier "{Binding namespace:DeviceType.All}" and "{Binding SomeDeviceTypeProperty.Value}"
  • no invalid values aka var invalid = (DeviceType)100;

example code

public class DeviceType
{
    public static readonly DeviceType
        Stb = new DeviceType("Stb", "Set Top Box", 19.95),
        Panel = new DeviceType("Panel", 99),
        Monitor = new DeviceType("Monitor", 19.95),
        Cable = Simple("Cable"),
        Connector = Simple("Connector"),
        WirelessKeyboard = new DeviceType("WirelessKeyboard", "Wireless Keyboard", 20);

    private static readonly IEnumerable<DeviceType> _all = typeof(DeviceType)
        .GetFields(BindingFlags.Public | BindingFlags.Static).Select(f => (DeviceType)f.GetValue(null)).ToArray();
    public static IEnumerable<DeviceType> All { get { return _all; } }

    public static DeviceType Parse(string name)
    {
        foreach (var item in All)
        {
            if (item.Name == name)
                return item;
        }
        throw new KeyNotFoundException(name);
    }

    private static DeviceType Simple(string name)
    {
        return new DeviceType(name, name, 9.95);
    }
    private DeviceType(string name, decimal value) : this(name, name, value) { }
    private DeviceType(string name, string description, decimal value)
    {
        Name = name;
        Description = description;
        Value = value;
    }

    public string Name { get; private set; }
    public string Description { get; private set; }
    public decimal Value { get; private set; }

    public override string ToString()
    {
        return Name;
    }
}
Firo
  • 30,626
  • 4
  • 55
  • 94
1

What you want to do is: Create an Attribute to describe the enume more specific: This is how you can do it:

public class EnumValue : Attribute
{
    public Decimal Value { get; private set; }
    public EnumValue(Decimal value)
    {
        this.Value = value;
    }
}

This can be used through this Extension Method:

private static Decimal GetEnumCustomAttribute(this Enum leEnum, Typ typ)
    {
        try
        {
            if (leEnum == null) throw new ArgumentNullException("leEnum");

            Type type = leEnum.GetType();

            MemberInfo[] memInfo = type.GetMember(leEnum.ToString());

            if (memInfo != null && memInfo.Length > 0)
            {
                object[] attrs = memInfo[0].GetCustomAttributes(typeof(EnumValue), false);

                if (attrs != null && attrs.Length > 0)
                    return ((EnumValue)attrs[0]).Value;
            }

            return Decimal.MinValue;
        }
        catch (Exception)
        {
            throw;
        }
    }

Give it a try!