1

I would like to have a list of enum values with extended data. The database will only save an integer value corresponding to my enum value, but in code I would like to have more information than just the enum name.

Let me give you an example:

public enum Reason
{
    NotEnoughStock, // Valid = yes, Responsability = company, Text = There is not enough stock
    ProductNotAvailabe, // Valid = no, Responsability = company, Text = Produc is not available
    PaymentNotDone, // Valid = no, Responsability = client, Text = Payment is not done
    BlackListedClient, // Valid = no, Responsability = client, Text = Client is black listed
    NotEnoughTime // Valid = yes, Responsability = company, Text = There is not enough time
}

How can I do that? Should I use something else than an enum? I like the enumeration.

Bastien Vandamme
  • 17,659
  • 30
  • 118
  • 200
  • 3
    See this post: https://stackoverflow.com/questions/28708537/how-do-i-add-multiple-attributes-to-an-enum – Kevin Nov 04 '19 at 04:59
  • C# enums do not have associated data (like they would in Swift, for example). However, you can use reflection and attributes or static properties to hold that data. It depends on the use case. Normally enums are used in c# to give meaning to numeric values transferred to another and/or storage system (e.g. database) – zaitsman Nov 04 '19 at 05:03

5 Answers5

3

You could:

  • use Attributes (C#); or
  • create helper class that returns the extra information, via something like Helper.GetExtraInfo(MyType val).

For the 'Text' value you could use DescriptionAttribute. For the other two I think you should create new attributes.

class ValidAttribute : Attribute
{
    public bool Valid { get; private set; }

    public ValidAttribute(bool valid)
    {
        this.valid = valid;
    }
}

Get enum from enum attribute contains an extension method to read values of attributes.

tymtam
  • 31,798
  • 8
  • 86
  • 126
  • Attributes seems a good solution yes. But Is there already something existing or should I create all from scratch. I know there is an attribute 'Description' but I can only have one 'Description'. Is it possible to have multiple attributes or an attribute that allows me to define multiple variables or should I create multiple different attributes? – Bastien Vandamme Nov 04 '19 at 05:52
  • For the other two I think you should create new attributes. – tymtam Nov 04 '19 at 06:35
2

With combined help of tymtam and Kevin I did this:

void Main()
{
    Reason.NotEnoughStock.GetAttributeOfType<DescriptionAttribute>().Description;
    Reason.ProductNotAvailabe.GetAttributeOfType<ResponsabilityAttribute>().Responsability;
}

public enum Reason
{
    [Description("There is not enough stock")]
    [Valid(true)]
    [Responsability("company")]
    NotEnoughStock, 

    [Description("Produc is not available")]
    [Valid(false)]
    [Responsability("company")]
    ProductNotAvailabe,

    [Description("Payment is not done")]
    [Valid(false)]
    [Responsability("client")]
    PaymentNotDone, 

    [Description("Client is black listed")]
    [Valid(false)]
    [Responsability("client")]
    BlackListedClient, 

    [Description("There is not enough time")]
    [Valid(true)]
    [Responsability("company")]
    NotEnoughTime 
}

public static class EnumHelper
{
    /// <summary>
    /// Gets an attribute on an enum field value
    /// </summary>
    /// <typeparam name="T">The type of the attribute you want to retrieve</typeparam>
    /// <param name="enumVal">The enum value</param>
    /// <returns>The attribute of type T that exists on the enum value</returns>
    /// <example>string desc = myEnumVariable.GetAttributeOfType<DescriptionAttribute>().Description;</example>
    public static T GetAttributeOfType<T>(this Enum enumVal) where T : System.Attribute
    {
        var type = enumVal.GetType();
        var memInfo = type.GetMember(enumVal.ToString());
        var attributes = memInfo[0].GetCustomAttributes(typeof(T), false);
        return (attributes.Length > 0) ? (T)attributes[0] : null;
    }
}

public class ValidAttribute : Attribute
{
    public bool Valid;
    public ValidAttribute(bool valid) { Valid = valid; }
}

public class ResponsabilityAttribute : Attribute
{
    public string Responsability;
    public ResponsabilityAttribute(string responsability) { Responsability = responsability; }
}
Bastien Vandamme
  • 17,659
  • 30
  • 118
  • 200
1

You could create an extension method for the enum so that you still can enjoy the enum and getting data in Reason object.

var reason = Reason.NotEnoughStock.GetReasonInfo(ReasonData);

Sample

internal class Program
{
    public static void Main(string[] args)
    {
        var reason = ReasonConstant.Reason.NotEnoughStock.GetReasonInfo(ReasonData);
    }
}

public static class ReasonUtils
{
    public static ReasonInfo GetReasonInfo(this ReasonConstant.Reason reason, Dictionary<ReasonConstant.Reason, ReasonInfo> reasonData)
    {
        if (reasonData == null)
            return null;
        if (!reasonData.ContainsKey(reason))
            return null;
        else
            return reasonData[reason];
    }
}

public class ReasonInfo
{
    public bool IsValid { get; set; }
    public string Responsability { get; set; }
    public string Text { get; set; }
}

public static class ReasonConstant
{
    public enum Reason
    {
        NotEnoughStock, // Valid = yes, Responsability = company, Text = There is not enough stock
        ProductNotAvailabe, // Valid = no, Responsability = company, Text = Produc is not available
        PaymentNotDone, // Valid = no, Responsability = client, Text = Payment is not done
        BlackListedClient, // Valid = no, Responsability = client, Text = Client is black listed
        NotEnoughTime // Valid = yes, Responsability = company, Text = There is not enough time
    }
}
Ray Krungkaew
  • 6,652
  • 1
  • 17
  • 28
0

There is nothing available to achieve your goal in a simple way.

You must implement by hand the behavior needed.

You may consider to create a class to have a good and proper design as well as to be able to use in simply and efficiently because in fact you ask how to create a dictionary of instances keyed by an enum value.

So having:

Reason.cs

public enum Reason
{
  NotEnoughStock,
  ProductNotAvailabe,
  PaymentNotDone,
  BlackListedClient,
  NotEnoughTime
}

You can create this enum:

Responsability.cs

public enum Responsability
{
  None,
  Company,
  Client
}

And this class that allows storing metadata of an enum in an instance as well as offers a static list of all metadata of the enum type that is initialized in the static constructor:

ReasonMetaData.cs

using System.Collections.Generic;

public class ReasonMetaData
{

  static public Dictionary<Reason, ReasonMetaData> All { get; private set; }
  static ReasonMetaData()
  {
    All = new Dictionary<Reason, ReasonMetaData>();
    All.Add(
      Reason.NotEnoughStock,
      new ReasonMetaData(Responsability.Company, true, "There is not enough stock"));
    All.Add(
      Reason.ProductNotAvailabe,
      new ReasonMetaData(Responsability.Company, false, "Product is not available"));
    All.Add(
      Reason.PaymentNotDone,
      new ReasonMetaData(Responsability.Client, false, "Payment is not done"));
    All.Add(
      Reason.BlackListedClient,
      new ReasonMetaData(Responsability.Client, false, "Client is black listed"));
    All.Add(
      Reason.NotEnoughTime,
      new ReasonMetaData(Responsability.Company, true, "There is not enough time"));
  }
  static public ReasonMetaData Get(Reason reason)
  {
    if ( All.ContainsKey(reason) )
      return All[reason];
    else
      return null;
  }
  public Responsability Responsability { get; private set; }
  public bool Valid { get; private set; }
  public string Text { get; private set; }

  public override string ToString()
  {
    return $"Responsability = {Enum.GetName(typeof(Responsability), Responsability)}, " +
           $"Valid = {( Valid ? "yes" : "no" )}" +
           $"Text = {Text}";
  }

  private ReasonMetaData()
  {
  }

  private ReasonMetaData(Responsability responsability, bool valid, string text)
  {
    Responsability = responsability;
    Valid = valid;
    Text = text;
  }
}

You can also create an extension method as suggested by @RawitasKrungkaew:

ReasonMetadataHelper.cs

static public class ReasonMetadataHelper
{
  static public ReasonMetaData GetMetaData(this Reason reason)
  {
    return ReasonMetaData.Get(reason);
  }
}

Usage and test

static void Test()
{
  Console.WriteLine("Metadata of Reason.NotEnoughStock is:");
  Console.WriteLine(Reason.NotEnoughStock.GetMetaData());
  Console.WriteLine("");
  Console.WriteLine("Metadata of Reason.ProductNotAvailabe is:");
  Console.WriteLine(ReasonMetaData.Get(Reason.ProductNotAvailabe));
  Console.WriteLine("");
  Console.WriteLine("All metadata of Reason enum are:");
  foreach ( var item in ReasonMetaData.All )
    Console.WriteLine($"  {item.Key}: {item.Value}");
}

Output

Metadata of Reason.NotEnoughStock is:
Responsability = Company, Valid = yes, Text = There is not enough stock

Metadata of Reason.ProductNotAvailabe is:
Responsability = Company, Valid = no, Text = Product is not available

All metadata of Reason enum are:
  NotEnoughStock: Responsability = Company, Valid = yes, Text = There is not enough stock
  ProductNotAvailabe: Responsability = Company, Valid = no, Text = Product is not available
  PaymentNotDone: Responsability = Client, Valid = no, Text = Payment is not done
  BlackListedClient: Responsability = Client, Valid = no, Text = Client is black listed
  NotEnoughTime: Responsability = Company, Valid = yes, Text = There is not enough time

Improvement

You can mix this solution with attributes to create the list automatically by parsing attributes of enum items:

So having:

ReasonAttributes.cs

public class ReasonResponsabilityAttribute : Attribute
{
  public Responsability Responsability { get; private set; }
  public ReasonResponsabilityAttribute(Responsability responsability)
  {
    Responsability = responsability;
  }
}

public class ReasonValidAttribute : Attribute
{
  public bool Valid { get; private set; }
  public ReasonValidAttribute(bool valid)
  {
    Valid = valid;
  }
}

public class ReasonTextAttribute : Attribute
{
  public string Text { get; private set; }
  public ReasonTextAttribute(string text)
  {
    Text = text;
  }
}

Reason.cs is now:

public enum Reason
{

  [ReasonResponsability(Responsability.Company)]
  [ReasonValid(true)]
  [ReasonText("There is not enough stock")]
  NotEnoughStock,

  // ...
  ProductNotAvailabe,

  // ...
  PaymentNotDone,

  // ...
  BlackListedClient,

  // ...
  NotEnoughTime

}

ReasonMetadata.cs static constructor is now:

using System.Reflection;

static ReasonMetaData()
{
  All = new Dictionary<Reason, ReasonMetaData>();
  var list = Enum.GetValues(typeof(Reason));
  foreach ( Reason reason in list )
  {
    var metadata = new ReasonMetaData();
    var memberinfo = reason.GetType().GetMember(Enum.GetName(typeof(Reason), reason));
    if ( memberinfo.Length == 1 )
    {
      var attributes = memberinfo[0].GetCustomAttributes();
      foreach ( Attribute attribute in attributes )
        if ( attribute is ReasonResponsabilityAttribute )
          metadata.Responsability = ( (ReasonResponsabilityAttribute)attribute ).Responsability;
        else
        if ( attribute is ReasonValidAttribute )
          metadata.Valid = ( (ReasonValidAttribute)attribute ).Valid;
        else
        if ( attribute is ReasonTextAttribute )
          metadata.Text = ( (ReasonTextAttribute)attribute ).Text;
    }
    All.Add(reason, metadata);
  }
}

Using only one attribute

ReasonMetadataAttribute.cs

public class ReasonMetadataAttribute : Attribute
{
  public Responsability Responsability { get; private set; }
  public bool Valid { get; private set; }
  public string Text { get; private set; }
  public ReasonMetadataAttribute(Responsability responsability, bool valid, string text)
  {
    Responsability = responsability;
    Valid = valid;
    Text = text;
  }
}

ReasonMetadata.cs static constructor is now:

static ReasonMetaData()
{
  All = new Dictionary<Reason, ReasonMetaData>();
  var list = Enum.GetValues(typeof(Reason));
  foreach ( Reason reason in list )
  {
    var metadata = new ReasonMetaData();
    var memberinfo = reason.GetType().GetMember(Enum.GetName(typeof(Reason), reason));
    if ( memberinfo.Length == 1 )
    {
      var attributes = memberinfo[0].GetCustomAttributes();
      foreach ( Attribute attribute in attributes )
        if ( attribute is ReasonMetadataAttribute )
        {
          var metadataAttribute = (ReasonMetadataAttribute)attribute;
          metadata.Responsability = metadataAttribute.Responsability;
          metadata.Valid = metadataAttribute.Valid;
          metadata.Text = metadataAttribute.Text;
        }
    }
    All.Add(reason, metadata);
  }
}

Usage:

public enum Reason
{
  [ReasonMetadata(Responsability.Company, true, "There is not enough stock")]
  NotEnoughStock,
  // ...
  ProductNotAvailabe,
  // ...
  PaymentNotDone,
  // ...
  BlackListedClient,
  // ...
  NotEnoughTime
}
-1

Create a class to store other information e.g ReasonDetail, then use Dictionary<Reason, ReasonDetail> and provide the mapping between the Reason and ReasonDetail

Ramesh
  • 13,043
  • 3
  • 52
  • 88