I have a need to represent a finite set of discrete string values:
- When these strings are used in code, they must belong to the valid set, or serious errors (or worse) can occur.
- As "magic strings" are a terrible code smell, I want to encapsulate the strings within a non-string type, so that I can require the use of my type instead of just a string and thus enforce at compile-time that a value is given that I know will work.
- No constants; if I change the string behind the scenes I should not have to recompile all usages of my type.
- The identifiers representing the valid values should not have to have any formulaic similarity to the actual string (no Enum.ToString or similar).
- Lastly, while I want to enforce the use of my type so I can restrict values to the ones I know are valid, the type must be easily convertible to a string so that the piece of code that really needs the string doesn't have to undergo a complicated or esoteric conversion.
What I came up with is a variation of a "multiton"; a class that cannot be instantiated in code, but has a finite number of static instances each representing a valid value of the type. They are implicitly convertible to strings and so the static members just drop in wherever a string is needed.
//abstract base; derive to create your specific set of values
public abstract class StringEnum
{
protected StringEnum(string name)
{
this.name = name;
}
private readonly string name;
public static implicit operator string(StringEnum sp)
{
return sp.name;
}
}
//one possible derivation
public class CommandNames:StringEnum
{
//unfortunately each derivation has to call back to the base constructor;
//it would be nice not to have to do this, but meh
protected CommandNames(string commName) : base(commName) { }
//obviously the instance names don't have to match the real string value exactly
public static readonly CommandNames DoSomething = new CommandNames("system638_Command_doSomething");
}
//example usage
public void ExecuteCommand(CommandNames name)
{
//assume ExecuteByName expects a string
CommandExecutor.ExecuteByName(name); //the StringEnum just drops in
}
...
//Only one of the static instances, with string values I control,
//can be passed into this function, thus safely encapsulating the inner
//objects' use of "magic strings".
ExecuteCommand(CommandNames.DoSomething);
Thoughts?