2012-04-24 13 views
11

Ho spesso bisogno di una mappatura hard-coded globale tra un enum e un altro oggetto (una stringa in questo esempio). Voglio co-individuare l'enum e mappare le definizioni per chiarire la manutenzione.Mantenere il mapping enum-to-object con la classe enum?

Come si può vedere, in questo esempio, viene creata una classe fastidiosa con un campo statico.

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"); 
    } 
} 

A volte la classe di mappatura può avere più funzioni e un nome significativo, e l'attività di mappatura può anche essere privato. Ma questo solo inquina il problema di manutenzione/correlazione.

Qualcuno ha un buon modello per questo?

risposta

6

È possibile utilizzare gli attributi per annotare l'enumerazione e quindi utilizzare la riflessione per creare il dizionario.

[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); 
    } 

} 

Se fate questo molto è possibile creare una funzione generica più generale che unisce valori di enumerazione con un attributo associato a quel valore di enumerazione:

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)) 
    ) 
    ); 
} 

e usarlo in questo modo:

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

Per divertirsi ancora di più è possibile creare un metodo di estensione:

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) 
    ); 
    } 

} 

Quindi chiamare EmailTemplate.ConfirmEmail.FileName() restituirà File2.htm.

+0

credo che vogliano rimuovere l'uso di classe, con una sintassi simile 'stringa sFile = EmailTemplate.Confirm.TemplateFile;'. – AMissico

+0

Martin! Grazie! Gli attributi sono un'ottima soluzione per la maggior parte delle mie situazioni (quelle in cui una stringa è appropriata). So di avere casi in cui un oggetto complesso è mappato su ogni enumerazione, ma potrebbe anche coprire alcuni scalari. – shannon

+0

Questo diventa più fantastico più lo uso. Grazie ancora Martin. – shannon

2

Normalmente, quando si desidera aggiungere informazioni o comportamenti extra ai propri elementi enumerazione, ciò significa che è necessaria una classe completa. È possibile prendere in prestito da (vecchiaia) Java il type-safe modello enum e creare qualcosa di simile:

sealed class EmailTemplate { 
    public static readonly EmailTemplate Welcome = new EmailTemplate("File1.html"); 
    public static readonly EmailTemplate Confirm = new EmailTemplate("File2.html"); 

    private EmailTemplate(string location) { 
    Location = location; 
    } 
    public string Location { get; private set; } 

    public string Render(Model data) { ... } 
} 

Ora è possibile associare qualsiasi proprietà o metodi per gli elementi, come Location e Render sopra.

+0

Grazie, anche una bella strategia. Riempie il vuoto all'altro capo dello spettro. – shannon

2

Ecco un approccio che ha funzionato molto bene per me.

public class BaseErrWarn : Attribute 
{ 
    public string Code { get; set; } 
    public string Description { get; set; } 

    public BaseErrWarn(string code, string description) 
    { 
     this.Code = code; 
     this.Description = description; 
    } 
} 

public enum ErrorCode 
{ 
    [BaseErrWarn("ClientErrMissingOrEmptyField", "Field was missing or empty.")] ClientErrMissingOrEmptyField, 
    [BaseErrWarn("ClientErrInvalidFieldValue", "Not a valid field value.")] ClientErrInvalidFieldValue, 
    [BaseErrWarn("ClientErrMissingValue", "No value passed in.")] ClientErrMissingValue 
} 

Ora è possibile utilizzare la riflessione per mappare l'Enum alla classe BaseErrWarn:

public static BaseErrWarn GetAttribute(Enum enumVal) 
{ 
    return (BaseErrWarn)Attribute.GetCustomAttribute(ForValue(enumVal), typeof(BaseErrWarn)); 
} 

private static MemberInfo ForValue(Enum errorEnum) 
{ 
    return typeof(BaseErrWarn).GetField(Enum.GetName(typeof(BaseErrWarn), errorEnum)); 
} 

Ecco un esempio che utilizza questa mappatura per mappare un Enum a un oggetto e poi tirare i campi fuori di esso :

public BaseError(Enum errorCode) 
    { 
     BaseErrWarn baseError = GetAttribute(errorCode); 
     this.Code = baseError.Code; 
     this.Description = baseError.Description; 
    } 

    public BaseError(Enum errorCode, string fieldName) 
    { 
     BaseErrWarn baseError = GetAttribute(errorCode); 
     this.Code = baseError.Code; 
     this.Description = baseError.Description; 
     this.FieldName = fieldName; 
    } 
+0

Un buon approccio con un controllo più granulare sugli attributi e meno da fare in termini di riflessione> mappatura> forma leggibile. –

Problemi correlati