Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep enum-to-object mapping with enum class?

I frequently need a global hard-coded mapping between an enum and another object (a string in this example). I want to co-locate the enum and mapping definitions to clarify maintenance.

As you can see, in this example, an annoying class with one static field is created.

public enum EmailTemplates
{
    // Remember to edit the corresponding mapping singleton!
    WelcomeEmail,
    ConfirmEmail
}

public class KnownTemplates
{
    public static Dictionary<EmailTemplates, string> KnownTemplates;
    static KnownTemplates() {
        KnownTemplates.Add(EmailTemplates.WelcomeEmail, "File1.htm");
        KnownTemplates.Add(EmailTemplates.ConfirmEmail, "File2.htm");
    }
}

Sometimes the mapping class can have more function and a meaningful name, and the mapping activity can even be private. But that only pollutes the maintenance/correlation problem.

Anyone have a good pattern for this?

like image 831
shannon Avatar asked Apr 24 '12 23:04

shannon


People also ask

Can enum be used for mapping?

A specialized Map implementation for use with enum type keys. All of the keys in an enum map must come from a single enum type that is specified, explicitly or implicitly, when the map is created. Enum maps are represented internally as arrays. This representation is extremely compact and efficient.

Can enum inherit enum?

This is not possible. Enums cannot inherit from other enums. In fact all enums must actually inherit from System.

Can enum inherit from class?

Although you can't inherit a superclass when declaring an enum, all enumerations automatically inherit one: java. lang.

Can we use enum as key in HashMap?

In HashMap, there is no constraint. You can use Enum as well as any other Object as key.


1 Answers

You can use attributes to annotate the enumeration and then use reflection to build the dictionary.

[AttributeUsage(AttributeTargets.Field)]
sealed class TemplateAttribute : Attribute {

  public TemplateAttribute(String fileName) {
    FileName = fileName;
  }

  public String FileName { get; set; }

}

enum EmailTemplate {

  [Template("File1.htm")]
  WelcomeEmail,

  [Template("File2.htm")]
  ConfirmEmail

}

class KnownTemplates {

  static Dictionary<EmailTemplate, String> knownTemplates;

  static KnownTemplates() {
    knownTemplates = typeof(EmailTemplates)
      .GetFields(BindingFlags.Static | BindingFlags.Public)
      .Where(fieldInfo => Attribute.IsDefined(fieldInfo, typeof(TemplateAttribute)))
      .Select(
        fieldInfo => new {
          Value = (EmailTemplate) fieldInfo.GetValue(null),
          Template = (TemplateAttribute) Attribute
            .GetCustomAttribute(fieldInfo, typeof(TemplateAttribute))
        }
      )
      .ToDictionary(x => x.Value, x => x.Template.FileName);
  }

}

If you do this a lot you can create a more general generic function that combines enumeration values with an attribute associated with that enumeration value:

static IEnumerable<Tuple<TEnum, TAttribute>> GetEnumAttributes<TEnum, TAttribute>()
  where TEnum : struct
  where TAttribute : Attribute {
  return typeof(TEnum)
    .GetFields(BindingFlags.Static | BindingFlags.Public)
    .Where(fieldInfo => Attribute.IsDefined(fieldInfo, typeof(TAttribute)))
    .Select(
      fieldInfo => Tuple.Create(
        (TEnum) fieldInfo.GetValue(null),
        (TAttribute) Attribute.GetCustomAttribute(fieldInfo, typeof(TAttribute))
      )
    );
}

And use it like this:

knownTemplates = GetEnumAttributes<EmailTemplate, TemplateAttribute>()
  .ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2.FileName);

For even more fun you can create an extension method:

static class EmailTemplateExtensions {

  static Dictionary<EmailTemplate, String> templates;

  static EmailTemplateExtensions() {
    templates = GetEnumAttributes<EmailTemplate, TemplateAttribute>()
      .ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2.FileName);
  }

  public static String FileName(this EmailTemplate emailTemplate) {
    String fileName;
    if (templates.TryGetValue(emailTemplate, out fileName))
      return fileName;
    throw new ArgumentException(
      String.Format("No template defined for EmailTemplate.{0}.", emailTemplate)
    );
  }

}

Then calling EmailTemplate.ConfirmEmail.FileName() will return File2.htm.

like image 165
Martin Liversage Avatar answered Sep 28 '22 17:09

Martin Liversage