Manage a list of Constants - c#

In several projects, we have a list of constant values in the database. These each have a table, name, and a GUID value. For example a Ticket Status table might have 3 values, "Open", "Closed" and "Hold". In our C# code a framework generates a C# file, as follows.
public class TicketStatus {
public static Guid Open = new Guid( "7ae15a71-6514-4559-8ea6-06b9ddc7a59a");
public static Guid Closed = new Guid( "41f81283-57f9-4bda-a03c-f632bd4d1628");
public static Guid Hold = new Guid( "41bcc323-258f-4e58-95be-e995a78d2ca8");
}; // end of TicketStatus
This allows us to write some clean(ish) code that sets ticket status as follows
ticket.strStatus = TicketStatus.Open.ToString();
While this works:
- It produces pretty clean C# code that's easy to ready and maintain
- It's supported by Intellisense
it's still clumsy, in that
- We have to continually convert to string for many operations
- The use of GUIDs seems like overkill.
- We cannot write a "normal" switch statement
// This won't compile
switch( strStatus ) {
case TicketStatus.Open:
case TicketStatus.Closed:
// do some stuff.
break;
}
The code was originally implemented with a bunch of GUIDs, to manage the case when a database would return the values in all upper case.
The question: What's the best way to code these constant values, so that it supports IntelliSense and switch statements?

Thanks Kirk,
Here's the string solution that I'm using.
public static class TicketStatus {
public const string Open = "7ae15a71-6514-4559-8ea6-06b9ddc7a59a";
public const string Closed = "41f81283-57f9-4bda-a03c-f632bd4d1628";
public const string Hold = "41bcc323-258f-4e58-95be-e995a78d2ca8";
}; // end of TicketStatus
string strTemp = TicketStatus.Open;
switch (strTemp) {
case TicketStatus.Open:
strTemp = "Jackpot";
break;
}

Preamble
I do really think, that you should stick to this for as long as you can.
public static class TicketStatus {
public const string Open = "7ae15a71-6514-4559-8ea6-06b9ddc7a59a";
public const string Closed = "41f81283-57f9-4bda-a03c-f632bd4d1628";
public const string Hold = "41bcc323-258f-4e58-95be-e995a78d2ca8";
}; // end of TicketStatus
If you want some magic :)
There is a solution, that nobody has mentioned here. You can use attributes to assign custom values to enumerations. You need to define an attribute and some helper class:
[AttributeUsage(AttributeTargets.Field)]
public class GuidValue : Attribute
{
public Guid Guid
{
get;
private set;
}
public GuidValue(Guid guid)
{
this.Guid = guid;
}
public GuidValue(string stringGuid)
{
this.Guid = new Guid(stringGuid);
}
}
public static class GuidBackedEnums
{
private static Guid GetGuid(Type type, string name)
{
return type.GetField(name).GetCustomAttribute<GuidValue>().Guid;
}
public static Guid GetGuid(Enum enumValue)
{
Type type = enumValue.GetType();
if (!type.IsEnum)
throw new Exception();
return GetGuid(type, enumValue.ToString());
}
public static T CreateFromGuid<T>(Guid guid)
{
Type type = typeof(T);
if (!type.IsEnum)
throw new Exception();
foreach (var value in Enum.GetValues(type))
{
if (guid == GetGuid(type, value.ToString()))
return (T)value;
}
throw new Exception();
}
}
And then you can use it in the following way:
enum TicketStatus
{
[GuidValue("7ae15a71-6514-4559-8ea6-06b9ddc7a59a")]
Open,
[GuidValue("41f81283-57f9-4bda-a03c-f632bd4d1628")]
Closed,
[GuidValue("41bcc323-258f-4e58-95be-e995a78d2ca8")]
Hold
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(GuidBackedEnums.CreateFromGuid<TicketStatus>(new Guid("41f81283-57f9-4bda-a03c-f632bd4d1628")));
Console.WriteLine(GuidBackedEnums.GetGuid(TicketStatus.Hold));
}
}
And, of course, TicketStatus is an ordinary enum. So you can use it in switch statements.

I would use a java-like enum and some reflection. Here's an example C# implementation. It my not work with a switch, but it will quickly identify for you the required object.
using System;
using System.Reflection;
using System.Linq;
public class TicketStatus
{
private string _guid;
private TicketStatus(string guid)
{
_guid = guid;
}
public string GuidValue {get {return _guid; } }
public static readonly TicketStatus Open = new TicketStatus("7ae15a71-6514-4559-8ea6-06b9ddc7a59a");
public static readonly TicketStatus Closed = new TicketStatus("41f81283-57f9-4bda-a03c-f632bd4d1628");
public static readonly TicketStatus Hold = new TicketStatus("41bcc323-258f-4e58-95be-e995a78d2ca8");
//Reads all static readonly fields and selects the one who has the specified GUID
public static TicketStatus Identify(string guid)
{
var ticket = typeof(TicketStatus).GetFields()
.Where(x => (x.IsStatic == true) && (x.IsInitOnly == true) )
.Select(x => x.GetValue(null))
.SingleOrDefault(x => (x as TicketStatus).GuidValue == guid)
as TicketStatus;
return ticket;
}
}
public class Program
{
public static void Main()
{
var guid = "7ae15a71-6514-4559-8ea6-06b9ddc7a59a";
var ticket = TicketStatus.Identify(guid);
if(ticket != null)
{
Console.WriteLine(ticket.GuidValue + " found");
}
else
{
Console.WriteLine("unknown ticket");
}
}
}

try this
public static Guid Open = new Guid("7ae15a71-6514-4559-8ea6-06b9ddc7a59a");
public static Guid Closed = new Guid("41f81283-57f9-4bda-a03c-f632bd4d1628");
public static Guid Hold = new Guid("41bcc323-258f-4e58-95be-e995a78d2ca8");
public enum Status1
{
Open,
Close,
Hold
}
public static Dictionary<Guid, Status1> Dic = new Dictionary<Guid, Status1>()
{
{Open , Status1.Open},
{Closed , Status1.Close},
{Hold , Status1.Hold}
};
and then
var a = TicketStatus.Closed;
var label = TicketStatus.Dic.FirstOrDefault(e => e.Key == a).Value;
switch (label)
{
case TicketStatus.Status1.Close:
}
to keep your code more readable

Related

C# How to use lambda expression with dictionary's value which is a method

I'm creating a program which will execute a command after user input.
Some commands I want to implement are: creating, reading a file, getting current working directory etc.
I created a dictionary which will store user input and corresponding command:
public static Dictionary<string, Action<string[]>> Commands { get; set; } = new Dictionary<string, Action<string[]>>()
{
{"pwd", PrintWorkingDirectory },
{"create", CreateFile },
{"print", ReadFile },
};
Unfortunately I have issues with triggering the method:
public void Run()
{
Console.WriteLine("Welcome, type in command.");
string input = null;
do
{
Console.Write("> ");
input = Console.ReadLine();
Execute(input);
} while (input != "exit");
}
public int Execute(string input)
{
if(Commands.Keys.Contains(input))
{
var action = Commands.Values.FirstOrDefault(); //doesn't work, gives '{command} not found'
}
Console.WriteLine($"{input} not found");
return 1;
}
Also I noticed that this solution would not work with method which is not void, but returns something, as for example CreateFile.
public static string CreateFile(string path)
{
Console.WriteLine("Create a file");
string userInput = Console.ReadLine();
try
{
string[] file = userInput.Split(new char[] { ' ' }).Skip(1).ToArray();
string newPath = Path.GetFullPath(Path.Combine(file));
using (FileStream stream = new FileStream(newPath, FileMode.Create, FileAccess.ReadWrite))
{
stream.Close();
}
using (StreamWriter sw = new StreamWriter(newPath))
{
Console.WriteLine("Please type the content.Press Enter to save.");
sw.WriteLine(Console.ReadLine());
sw.Close();
Console.WriteLine("File {0} has been created", newPath);
}
}
catch (Exception)
{
throw;
}
return path;
}
public static void ReadFile(string[] args)
{
Console.WriteLine("Reading file");
string userInput = Console.ReadLine();
string[] file = userInput.Split(new char[] { ' ' }).Skip(1).ToArray();
string newPath = Path.GetFullPath(Path.Combine(file));
string[] lines = File.ReadAllLines(newPath);
foreach (string line in lines)
Console.WriteLine(line);
}
public static void PrintWorkingDirectory(string[] args)
{
var currentDirectory = Directory.GetCurrentDirectory();
Console.WriteLine(currentDirectory);
}
Could somebody advise me how to deal with these issues?
Is it that this dictionary I created does not make much sense at all?
First problem: You're always fetching the first element of the dictionary and are not using the index operator to retrieve the correct value. Therefore change:
if(Commands.Keys.Contains(input))
{
var action = Commands.Values.FirstOrDefault(); //doesn't work, gives '{command} not found'
}
to:
public int Execute(string input)
{
if (Commands.Keys.Contains(input))
{
var action = Commands[input]; //doesn't work, gives '{command} not found'
action?.Invoke(new string[] { });
}
else
{
Console.WriteLine($"{input} not found");
}
return 1;
}
Regarding to your second question about dictionary usage. I think it is ok to use a dictionary to map different commands based on a given key. The alternative would be switch or if constructs, which can be prevented in Object Oriented Programming.
Regarding to your question about string CreateFile(string path). Since C# is strongly typed language your dictionary can only contain objects of type Action<string[]>, so you can't use methods with another signature than that. One solution is to add another dictionary in the form of Dictionary<string,Func<string[], string>. As a result you'll get more and more dictionaries depending on your method signatures. From here on you should think to build to encapsulate your commands in an e.g. CommandInterpreter class, that could offer an API like that:
void Request(string cmdName, string[] cmdParameters);
string GetLastResult();
int GetLastCode();
Update:
Below code shows a possible object oriented solution (I've left out interfaces to make the code more compact):
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp1
{
public class Command<T>
{
public string Name { get; }
public T TheCommand { get; }
public Command(string name, T theCommand)
{
Name = name;
TheCommand = theCommand;
}
}
public interface ICommandResult
{
void Ok(Action<ICommandResult> yes, Action<ICommandResult> no);
int Code { get; }
string Description { get; }
}
public abstract class CommandResult : ICommandResult
{
public int Code { get; }
public string Description { get; }
protected CommandResult(int code, string description)
{
Code = code;
Description = description;
}
public abstract void Ok(Action<ICommandResult> yes, Action<ICommandResult> no);
}
public class NullCommandResult : CommandResult
{
public NullCommandResult() : base(-1, "null")
{
}
public override void Ok(Action<ICommandResult> yes, Action<ICommandResult> no) => no?.Invoke(this);
}
public class SuccessCommandResult : CommandResult
{
public SuccessCommandResult(string description) : base(0, description)
{
}
public override void Ok(Action<ICommandResult> yes, Action<ICommandResult> no) => yes?.Invoke(this);
}
public class CommandInterpreter
{
private Dictionary<string, Func<IEnumerable<string>, ICommandResult>> Commands = new Dictionary<string, Func<IEnumerable<string>, ICommandResult>>();
public void RegisterCommand(Command<Func<IEnumerable<string>, ICommandResult>> cmd)
=> Commands.Add(cmd.Name, cmd.TheCommand);
public ICommandResult RunCommand(string name, IEnumerable<string> parameters)
=> Commands.Where(kvp => kvp.Key.Equals(name))
.Select(kvp => kvp.Value)
.DefaultIfEmpty(strArr => new NullCommandResult())
.Single()
.Invoke(parameters);
}
class Program
{
private CommandInterpreter _cmdInterpreter;
private Program()
{
_cmdInterpreter = new CommandInterpreter();
_cmdInterpreter.RegisterCommand(new Command<Func<IEnumerable<string>, ICommandResult>>("pwd", PrintWorkingDirectory));
_cmdInterpreter.RegisterCommand(new Command<Func<IEnumerable<string>, ICommandResult>>("create", CreateFile));
_cmdInterpreter.RegisterCommand(new Command<Func<IEnumerable<string>, ICommandResult>>("print", ReadFile));
}
private static CommandResult ReadFile(IEnumerable<string> arg) => new SuccessCommandResult("File read");
private static CommandResult CreateFile(IEnumerable<string> arg) => new SuccessCommandResult("File xyz created");
private static CommandResult PrintWorkingDirectory(IEnumerable<string> arg) => new SuccessCommandResult("Printed something");
static void Main() => new Program().Run();
private void Run()
{
Console.WriteLine("Welcome, type in command.");
string input;
do
{
Console.Write("> ");
input = Console.ReadLine();
var cmdResult = _cmdInterpreter.RunCommand(input, Enumerable.Empty<string>());
cmdResult.Ok(
r => Console.WriteLine($"Success: {cmdResult.Code}, {cmdResult.Description}"),
r => Console.WriteLine($"FAILED: {cmdResult.Code}, {cmdResult.Description}"));
} while (input != "exit");
}
}
}
Output:
Welcome, type in command.
> pwd
Success: 0, Printed something
> create
Success: 0, File xyz created
> abc
FAILED: -1, null
>
You can just copy the code and play around with it.

Use regular expression from another method in C#

I separated my RegularExpression() method from my validate() method that has all the if statements. How can I use my regex codes if it's separated like this?
I'm fairly new to programming, and I'm still learning how to use methods.
public void Validate()
{
RegularExpression();
if (PhoneNumber_Regex.IsMatch(PhonNumb_txBox.Text) == false)
{
MessageBox.Show("Invalid cellphone number");
}
if (Email_Regex.IsMatch(Email_txBox.Text) == false)
{
MessageBox.Show("Invalid E-Mail");
}
}
public RegularExpression(object PhoneNumber_Regex)
{
var PhoneNumber_Regex = new Regex(#"^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$");
var Email_Regex = new Regex(#"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$.");
}
Add a static class where you will declare your regular expressions:
public static class MyRegexps
{
public static readonly Regex PhoneNumber_Regex = new Regex(#"^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$");
public static readonly Regex Email_Regex = new Regex(#"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$.");
}
Then use them in your caller method:
if (MyRegexps.PhoneNumber_Regex.IsMatch(PhonNumb_txBox.Text) == false)
{
MessageBox.Show("Invalid cellphone number");
}
In
public RegularExpression(object PhoneNumber_Regex)
{
var PhoneNumber_Regex = new Regex(#"^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$");
var Email_Regex = new Regex(#"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$.");
}
You declare 2 variables - but scope means those variables dont exist outside that call, so, they arent available for use.
However, if as part of the class you declared
readonly Regex Email_Regex = new Regex(#"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$.");
so you have a read only variable you could then use like you thought as part of any function within that class
if (Email_Regex.IsMatch(Email_txBox.Text) == false)
{
MessageBox.Show("Invalid cellphone number");
}
Should do something on this line.
public static class MyValidator
{
protected static PhoneNumberRegex = new Regex(#"^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$");
protected static EmailRegex = new Regex(#"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$.");
public static bool ValidatePhoneNumber(string strToMatch)
{
return PhoneNumberRegex.IsMatch(strToMatch);
}
public static bool ValidateEmail(string strToMatch)
{
return EmailRegex.IsMatch(strToMatch);
}
}
and then use it like this
if (!MyValidator.ValidatePhoneNumber(PhonNumb_txBox.Text))
{
MessageBox.Show("Invalid cellphone number");
}
While it's not the only way to share things between methods, in this particular case it would make sense to use class-level members.
Your regular expressions themselves are unlikely to change, and can probably be static.
Initialize them in a constructor so it's automatic instead of having to manually call the initializer.
This all adds up to something more like this:
public class MyClass
{
private static Regex PhoneNumber_Regex { get; set; }
private static Regex Email_Regex { get; set; }
static MyClass
{
PhoneNumber_Regex = new Regex(#"^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$");
Email_Regex = new Regex(#"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$.");
}
public void Validate()
{
if (!PhoneNumber_Regex.IsMatch(PhonNumb_txBox.Text))
MessageBox.Show("Invalid cellphone number");
if (!Email_Regex.IsMatch(Email_txBox.Text))
MessageBox.Show("Invalid E-Mail");
}
}

What is the datatype of "value" in "set" accessor?

I'm just wondering what is the datatype of value variable in C#'s set accessor?
Because I want to implement type-hinting in C#'s set accessor.
For example, I have a setter method:
public User
{
private string username;
public void setUsername(SingleWord username)
{
this.username = username.getValue(); // getValue() method from "SingleWord" class returns "string"
}
}
Now how do I implement this in C#'s accessor syntax?
public User
{
public string Username
{
get ;
set {
// How do I implement type-hinting here for class "SingleWord"?
// Is it supposed to be:
// this.Username = ((SingleWord)value).getValue(); ???
}
}
}
So that I can call it this way:
User newuser = new User() {
Username = new SingleWord("sam023")
};
Thanks in advance!
EDIT: Here's the source code of SingleWord:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Guitar32.Exceptions;
using Guitar32.Common;
namespace Guitar32.Validations
{
public class SingleWord : Validator, IStringDatatype
{
public static String expression = "^[\\w\\S]+$";
public static String message = "Spaces are not allowed";
private String value;
public SingleWord(String value, bool throwException = false) {
this.value = value;
if (throwException && value != null) {
if (!this.isValid()) {
throw new InvalidSingleWordException();
}
//if (this.getValue().Length > 0) {
// if (!this.isWithinRange()) {
// throw new Guitar32.Exceptions.OutOfRangeLengthException();
// }
//}
}
}
public int getMaxLength() {
return 99999;
}
public int getMinLength() {
return 1;
}
public String getValue() {
return this.value;
}
public bool isWithinRange() {
return this.getValue().Length >= this.getMinLength() && this.getValue().Length <= this.getMaxLength();
}
public override bool isValid() {
return this.getValue().Length > 0 ? Regex.IsMatch(this.getValue(), expression) : true;
}
}
public class InvalidSingleWordException : Exception {
public InvalidSingleWordException() : base("Value didn't comply to Single Word format")
{ }
}
}
I used this class to provide back-end validation by adding SingleWord as the datatype required from the setter.
The type of value is the type of the property, no matter what.
So in your example,
public string Username
{
...
set
{
value.GetType() // -> string
...
}
}
The simple solution for what you're looking for is to just call .getValue() on your SingleWord instance,
User newuser = new User()
{
Username = new SingleWord("sam023").getValue()
};
Or better yet, but I assume this won't work because of code you haven't shown us,
User newuser = new User()
{
Username = "sam023"
};
But if that's an absolute no-go, what it sounds like you're looking for is an implicit operator on SingleWord. If you have the ability to modify the class, you can add an operator that looks like this, and it'll automatically perform the conversion to a string such that you should be able to use the syntax you've listed.
public static implicit operator string(SingleWord d)
{
return d.getValue();
}

How to Create Two level enum

Sorry if the question's title is confusing,but i don't know how to ask it.
what is really want is to have read-only data that will never change.
currently i have two enums MeterType and SubMeterType
public enum MeterType
{
Water = 1001,
Electricity = 1004,
Gas = 1007
}
and
public enum SubMeterType
{
DrinkingWater = 1002,
UsageWater = 1003,
SubsidiseGas = 1008,
NonSusbsidisedGas = 1009
}
Now i would like to use these as follows
To get the MeterType
string meterType = MeterType.Water.ToString("d");
and to get the SubMeterType, is it possible to have something like
string subMeterType = MeterType.Water.DrinkingWater("d");
Shall go for another approach using classes with readonly properties ?
or modify these enums to suit my requirement.
Instead of using enums you might use constant integers in nested classes:
public static class MeterType
{
public const int Water = 1001;
public const int Electricity = 1004;
public const int Gas = 1007;
public static class Waters
{
public const int DrinkingWater = 1002;
public const int UsageWater = 1003;
}
public static class Gases
{
public const int SubsidiseGas = 1008;
public const int NonSusbsidisedGas = 1009;
}
}
Just use a nested enum:
public class MeterType
{
public enum Water { }
}
But in this case you can't use MeterType.Water directly, this is not possible by default. Try use nested objects then or a secondary enum for the MeterType.
public enum MeterType { }
public enum MeterTypeWater { }
In this case you need a property with a different name for each of the enums. Best solution is to not use a nested class:
public class MeterType
{
public static WaterType Water { get; }
}
public class WaterType
{
public readonly SubWaterType DrinkingWater = SubWaterType.DrinkingWater;
}
You cannot nest enums but you already know that. What you can do is to have const or readonly properties/fields which map to the various types you want. Then in each of the types, you define fields/properties for the subtypes.
public static class MeterTypes
{
public static readonly Electricity electricity;
public static readonly Gas gas;
public static readonly Water water;
static MeterTypes()
{
// initialize the meter types to their default
MeterTypes.Water = Water.GenericWater;
MeterTypes.Gas = Gas.GenericGas;
MeterTypes.Electricity = Electricity.GenericElectricity;
}
private MeterTypes()
{
// private initialization prevents others from creating the class
}
public class Electricity
{
public enum Type
{
Generic = 1007,
SubsidisedElectricity = 1008,
NonSubsidisedElectricity = 1009
}
public static readonly Electricity GenericElectricity;
public static readonly Electricity SubsidisedElectricity;
public static readonly Electricity NonSubsidisedElectricity;
private Type ElectricityType;
static Electricity()
{
SubsidisedElectricity = new Electricity(Type.SubsidisedElectricity);
NonSubsidisedElectricity = new Electricity(Type.NonSubsidisedElectricity);
GenericElectricity = new Electricity(Type.Generic);
}
// private constructor prevents creation from outside the class
private Electricity(Type ElectricityType)
{
this.ElectricityType = ElectricityType;
}
public override string ToString()
{
return ElectricityType.ToString();
}
public string ToString(string format)
{
return ElectricityType.ToString(format);
}
}
public class Gas
{
public enum Type
{
Generic = 1007,
SubsidisedGas = 1008,
NonSubsidisedGas = 1009
}
public static readonly Gas GenericGas;
public static readonly Gas SubsidisedGas;
public static readonly Gas NonSubsidisedGas;
private Type gasType;
static Gas()
{
SubsidisedGas = new Gas(Type.SubsidisedGas);
NonSubsidisedGas = new Gas(Type.NonSubsidisedGas);
GenericGas = new Gas(Type.Generic);
}
// private constructor prevents creation from outside the class
private Gas(Type gasType)
{
this.gasType = gasType;
}
public override string ToString()
{
return gasType.ToString();
}
public string ToString(string format)
{
return gasType.ToString(format);
}
}
public class Water
{
public enum Type
{
Generic = 1001,
DrinkingWater = 1002,
UsageWater = 1003
}
public static readonly Water GenericWater;
public static readonly Water DrinkingWater;
public static readonly Water UsageWater;
private Type waterType;
static Water()
{
DrinkingWater = new Water(Type.DrinkingWater);
UsageWater = new Water(Type.UsageWater);
GenericWater = new Water(Type.Generic);
}
// private constructor prevents creation from outside the class
private Water(Type waterType)
{
this.waterType = waterType;
}
public override string ToString()
{
return waterType.ToString();
}
public string ToString(string format)
{
return waterType.ToString(format);
}
}
}
This can be used as such
var w = MeterTypes.water; // will give generic water
var uw = MeterTypes.Water.UsageWater // will give usage water
and you get the added use of the Enum.ToString() methods too.
You'll have to note that this implementation relies on C#'s case sensitivity. This makes MeterTypes.electricity and MeterTypes.Electricity refer to a field and a class respectively. This code will is very likely to fail if it ever gets used in a language that is not case sensitive (e.g. VB.NET). You could circumvent this by using a different name for the static fields in the MeterTypes class (e.g. _Electricity instead of electricity).

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?
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.
Here is an approach which worked pretty well for 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
}
Now you can use reflection to map the Enum to the BaseErrWarn class:
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));
}
Here is an example which uses this mapping to map an Enum to an object and then pull fields off of it:
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;
}
Normally, when you want to add extra info or behaviors to your enum elements, that means you need a full blown class instead. You can borrow from (old-)Java the type-safe enum pattern and create something like this:
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) { ... }
}
Now you can associate any properties or methods to your elements, like Location and Render above.

Categories

Resources