Keep enum-to-object mapping with enum class?

c#, design-patterns, enums, mapping

Solution

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`.

Problem

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?

Original source