First and foremost: You are correct in avoiding magic numbers and strings.
A rule of thumb is that is its an integer that doesn't change (often) then enums are good. If its a string that doesn't change often then a static (shared) class with strings in it is good. Your goal in these cases is to centralize the definition. This gives you several advantages such as avoiding typos (in a string), making the code more readable (with enum names instead of numbers) and you can trace uses (find usages). Note that you may not want to use the const keyword unless your values are set in stone and will never change.
For values that change often you want to consider program logic to handle these. Nobody hardcodes customer names and adresses (hopefully), so if your statuses changes often you want to handle them as you would any other data. If they are tied to the program logic then you need to define what program logic is required and somehow link up the statuses.
For values that will change less frequently you have the choice between hardcoding (enum/string) and reading from config file. Hardcoding obviously requires recompiling and then you should consider the long-term cost. What takes you 5 minutes to do today may take days in the future. 2 years from now another developer assigned the task of updating one of these values may spend days setting up the right development environment, compiling and testing. Seeing how easy it is to just read the values from a file my recommendation would usually be to use config files.
For your particular problem it seems what you need is a translation table. Your application has a specific task to perform. The answer would seem to make the application generic and have the translation in a file. You want to keep both constraints on status updates (what will transform what statuses into what) and friendly displays (for users) in a file (or sql-table). In short making the application agnostic about what transitions the status fields go through.
Do not worry about file IO. Its easy to implement caching.
private static TranslationObject _translationObject = null;
public static TranslationObject GetTranslationObject() {
if (_translationObject == null)
lock (_translationObject)
_translationObject = JsonConvert.DeserializeObject<TranslationObject>(File.ReadAllTextt("TranslationTable.json"));
return _translationObject;
}
Even in web apps the app will stay alive for some time so this would cache the file between requests. And anyway the OS should have enough memory to cache the disk IO for a few kilobytes of data.
To illustrate one of many ways to implement I've added a sample where a struct is used in combination with Dictionary for fast two-way lookup between a friendly status and a combination of other statuses. I'm not sure if this is exactly what you are asking for, but hopefully it can be of some help.
void Main()
{
// Get translation
var translationObject = GetTranslationObject();
// Find friendly status based on combo
var friendly1 = translationObject.ComboStatusToFriendlyStatus[new StatusCombo(0, 30, 5)];
// Find combo based on friendly status
var combo1 = translationObject.FriendlyStatusToComboStatus[0];
}
public struct StatusCombo
{
// Please note that fields are readonly for immutability.
// This is particularly important since the GetHashCode() value is used in dictionaries.
// Note that status fields can also be strings (because we use .GetHashCode() in GetHashCode()).
public readonly int Status1;
public readonly int Status2;
public readonly int Status3;
[JsonConstructor]
public StatusCombo(int status1, int status2, int status3)
{
Status1 = status1;
Status2 = status2;
Status3 = status3;
}
public override int GetHashCode()
{
unchecked
{
int hashCode = Status1.GetHashCode();
hashCode = (hashCode * 397) ^ Status2.GetHashCode();
hashCode = (hashCode * 397) ^ Status3.GetHashCode();
// ... Repeat for every extra statuscode you add
return hashCode;
}
}
}
public class TranslationObject
{
public Dictionary<int, string> Status1Mapping;
public Dictionary<int, string> Status2Mapping;
public Dictionary<int, string> Status3Mapping;
public Dictionary<int, string> FriendlyStatus;
public Dictionary<int, StatusCombo> FriendlyStatusToComboStatus;
[JsonIgnore]
public Dictionary<StatusCombo, int> ComboStatusToFriendlyStatus;
}
private static TranslationObject _translationObject = null;
public static TranslationObject GetTranslationObject()
{
if (_translationObject == null)
lock ("Reading _translationObject")
{
_translationObject = JsonConvert.DeserializeObject<TranslationObject>(File.ReadAllText(@"TranslationTables.json"));
// Populate reverse lookup
_translationObject.ComboStatusToFriendlyStatus=new Dictionary<UserQuery.StatusCombo, int>();
foreach (var t in _translationObject.FriendlyStatusToComboStatus)
_translationObject.ComboStatusToFriendlyStatus.Add(t.Value, t.Key);
}
return _translationObject;
}
A sample JSON file:
{
"Status1Mapping": {
"0": "Status1_0",
"10": "Status1_1"
},
"Status2Mapping": {
"30": "Status2_0",
"55": "Status2_1"
},
"Status3Mapping": {
"5": "Status3_0",
"2": "Status3_1"
},
"FriendlyStatus": {
"0": "Submitted",
"1": "Received"
},
"FriendlyStatusToComboStatus": {
"0": {
"Status1": 10,
"Status2": 55,
"Status3": 2
},
"1": {
"Status1": 0,
"Status2": 30,
"Status3": 5
}
}
}
And the code I used to populate the sample JSON:
var tro = new TranslationObject();
tro.Status1Mapping = new Dictionary<int, string>();
tro.Status2Mapping = new Dictionary<int, string>();
tro.Status3Mapping = new Dictionary<int, string>();
tro.Status1Mapping.Add(0, "Status1_0");
tro.Status1Mapping.Add(10, "Status1_1");
tro.Status2Mapping.Add(30, "Status2_0");
tro.Status2Mapping.Add(55, "Status2_1");
tro.Status3Mapping.Add(5, "Status3_0");
tro.Status3Mapping.Add(2, "Status3_1");
tro.FriendlyStatus = new Dictionary<int, string>();
tro.FriendlyStatus.Add(0, "Submitted");
tro.FriendlyStatus.Add(1, "Received");
tro.FriendlyStatusToComboStatus = new Dictionary<int, UserQuery.StatusCombo>();
tro.FriendlyStatusToComboStatus.Add(0, new StatusCombo(10, 55, 2));
tro.FriendlyStatusToComboStatus.Add(1, new StatusCombo(0, 30, 5));
File.WriteAllText(@"TranslationTables.json", JsonConvert.SerializeObject(tro));