I have a set of enumeration values (fault codes to be precise). The code is a 16 bit unsigned integer. I am looking for a data structure that could represent such an enumeration. A similar question has been asked here: What's the best C# pattern for implementing a hierarchy with an enum?. But this hierarchy is deeper.
Sample enumeration valuesCurrent = 0x2000,
Current_DeviceInputSide = 0x2100,
ShortToEarth = 0x2120,
ShortToEarthInPhase1 = 0x2121,
ShortToEarthInPhase2 = 0x2122,
ShortToEarthInPhase3 = 0x2123
Use case
When the user provides a code then the UI has to display the equivalent meaning of the code with the hierarchy.
For example, if the user provides a value 0x2121
then the UI has to display Short to earth in phase 1 in the current at device input side
. The best way to represent this is by using a hierarchical notation: Current : DeviceInputSide : ShortToEarth : ShortToEarthInPhase1
.
Competing approaches
I have three competing approaches to represent the enumeration:
- Create an enumeration at each level of the hierarchy. Then use a controller class to resolve the name.
- Store the enumeration values in an xml and use LINQ to generate the meaning of the code.
- Store the enumeration values in an xml. During the application startup. Create a singleton instance to retrieve the meaning. The instance contains a dictionary populated with the values from the xml.
enum WarnCodes
{
None= 0x000,
Current = 0x2000
}
enum WarnCodes_Current
{
DeviceInputSide = 0x2100,
DeviceOutputSide = 0x2200
}
enum WarnCodes_Current_DeviceInputSide
{
ShortToEarth = 0x2120,
ShortCircuit = 0x2130
}
enum WarnCodes_Current_DeviceInputSide_ShortToEarth
{
InPhase1 = 0x2121,
InPhase2 = 0x2122
}
The controller:
public string GetMeaning(int code)
{
int bitMask = 0xF000;
int maskedCode = bitMask & code;
StringBuilder meaning = new StringBuilder();
switch (maskedCode)
{
case WarnCodes.Current:
meaning.Append("Current : ");
bitMask = 0xFF00;
maskedCode = bitMask & code;
switch (maskedCode)
{
case WarnCodes_Current.DeviceInputSide:
meaning.Append("Current : Device Input Side :");
...
break;
}
break;
...
}
}
Approach 2
The xml to store the enumeration values looks like this
<WarnCodes>
<code hex="2000" meaning="Current">
<code hex="2100" meaning="Current, Device Input side">
<code hex="2120" meaning="Short to Earth">
<code hex="2121" meaning="Short to earth in Phase L1"/>
<code hex="2122" meaning="Short to earth in Phase L2"/>
</code>
</code>
</code>
</WarnCodes>
And the method used to query the codes is:
XElement rootElement = XElement.Load(settingsFilePath);
public string GetHierarchicalMeaning(int code)
{
XElement rootElement = XElement.Load(warnCodesFilePath);
List<string> meanings = new List();
StringBuilder stringBuilder = new StringBuilder();
IEnumerable<XElement> elements;
elements = from el in rootElement.Descendants("code")
where (string)el.Attribute("hex") == code.ToString("X")
select el;
XElement element = elements.First();
while (element.Parent != null)
{
meanings.Add(element.Attribute("meaning").Value);
element = element.Parent;
}
meanings.Reverse();
foreach (string meaning in meanings)
{
stringBuilder.AppendFormat("{0} : ", meaning);
}
return stringBuilder.ToString().Trim().TrimEnd(':').Trim();
}
Approach 3
The xml to store the enumeration values is same as in Approach 2. The dictionary is populated from the xml
by GetChildren()
.
private Dictionary<int, WarnCodeValue> warnCodesDictionary;
public void Initialize()
{
XElement rootElement = XElement.Load(settingsFilePath);
warnCodesDictionary = GetChildren(rootElement);
}
private Dictionary<int, WarnCodeValue> GetChildren(XElement element)
{
if (element.Descendants().Count() > 0)
{
Dictionary<int, WarnCodeValue> childNodeDictionary = new Dictionary();
foreach (XElement childElement in element.Elements())
{
int hex = Convert.ToInt32(childElement.Attribute("hex").Value, 16);
string meaning = childElement.Attribute("meaning").Value;
Dictionary<int, WarnCodeValue> dictionary = GetChildren(childElement);
WarnCodeValue warnCodeValue;
if (dictionary == null)
{
warnCodeValue = new WarnCodeValue() {Meaning = meaning};
}
else
{
warnCodeValue = new WarnCodeValue() {Meaning = meaning, ChildNodes = dictionary};
}
childNodeDictionary.Add(hex, war开发者_C百科nCodeValue);
}
return childNodeDictionary;
}
return null;
}
The meanings are retrieved using GetHierarchicalMeaning()
:
public string GetHierarchicalMeaning(int code)
{
StringBuilder stringBuilder = new StringBuilder();
int firstLevel = code & 0xF000;
int secondLevel = code & 0xFF00;
int thirdLevel = code & 0xFFF0;
if(warnCodesDictionary.ContainsKey(firstLevel))
{
stringBuilder.AppendFormat("{0} : ", warnCodesDictionary[firstLevel].Meaning);
if (warnCodesDictionary[firstLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes.ContainsKey(secondLevel))
{
stringBuilder.AppendFormat("{0} : ", warnCodesDictionary[firstLevel].ChildNodes[secondLevel].Meaning);
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes.ContainsKey(thirdLevel))
{
stringBuilder.AppendFormat("{0} : ",
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].Meaning);
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes.ContainsKey(code))
{
stringBuilder.AppendFormat("{0} : ",
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes[code].Meaning);
}
}
}
}
}
The WarnCodeValue
class:
class WarnCodeValue
{
public string Meaning
{ get; set; }
public Dictionary<int, WarnCodeValue> ChildNodes { get; set; }
}
Questions
- Which of the above 3 approaches is better from a performance point of view?
- Are there any other approaches for representing the enumeration?
- Any improvements to the code?
Consider using classes instead of enums, you then use a singleton for each value and can use the type system to build a tree, including virtual methods to produce error txt etc. (This can sometimes be a good option, but can also lead you into lots of problems if it does not fit well)
You could use FlagsAttribute. For instance you could do something like this:
[FlagsAttribute]
enum WarnCodes
{
None= 0x0000,
Current = 0x2000,
// second level of hierarchy
DeviceInputSide = 0x0100,
DeviceOutputSide = 0x0200,
// third level of hierarchy
ShortToEarth = 0x0020,
ShortCircuit = 0x0030,
// fourth level of hierarchy
InPhase1 = 0x0001,
InPhase2 = 0x0002
}
You can test it like this:
int[] testVals = {0x0000, 0x2000, 0x2130, 0x2122, 0x2121, 0x2131};
foreach(var val in testVals)
{
Console.WriteLine( "{0,4:X} - {1}",
val, ( (WarnCodes)val ).ToString( ) );
}
Second attempt... You could implement your own tree structure where each node has a single-digit hexadecimal representation and a code like 0x2121 represents a branch of the tree:
>2 - (current)
/ \
(device input side)>1 2 (device output side)
/\ /\
>2 (short to earth)
/\
>1 (in phase 1)
So, to read what 0x2121 means, we follow the corresponding branch of the tree and (for each node) we read the message it contains.
Here's a quick and dirty implementation of the tree:
public class TreeNode
{
private List<TreeNode> _children;
public int hex {get; private set;}
public string meaning {get; private set;}
public IList<TreeNode> children {
get{
return _children.AsReadOnly();
}
}
public TreeNode(int hex, string meaning)
{
this.hex = hex;
this.meaning = meaning;
_children = new List<TreeNode>();
}
public TreeNode addChild(int hex, string meaning)
{
if(hex<=0 || hex >=16) throw new ArgumentOutOfRangeException("hex");
if(GetChildByCode(hex)!=null) throw new Exception("a child with code " +
hex.ToString() + " already exists");
var child = new TreeNode(hex,meaning);
_children.Add(child);
return child;
}
public TreeNode TryAddChild(int hex, string meaning)
{
if(hex<=0 || hex >=16) throw new ArgumentOutOfRangeException("hex");
var chd = GetChildByCode(hex);
if(chd==null) {
chd = new TreeNode(hex,meaning);
_children.Add(chd);
}
return chd;
}
public void AddBranch(int hexPath, string[] meanings)
{
var lst = intToList(hexPath,16,new LinkedList<int>()).ToList();
var curNode = this;
for(int i = 0; i<lst.Count; i++)
{
curNode = curNode.TryAddChild(lst[i], meanings[i]);
}
}
public TreeNode GetChildByCode(int hex)
{
return
(from c in _children
where c.hex == hex
select c).SingleOrDefault();
}
public string getMessagesByPath(int hexPath)
{
var lst = intToList(hexPath,16,new LinkedList<int>());
var msgs = getMessagesByPath(lst, new List<string>(),this);
return
(msgs == null || msgs.Count==0) ?
"None":
msgs.Aggregate((s1, s2) => s1 + ": " + s2);
}
// recursively follow the branch and read the node messages
protected IList<string> getMessagesByPath(LinkedList<int> hexPath, IList<string> accString, TreeNode curNode)
{
if(hexPath.Count == 0 || hexPath.First.Value == 0 || curNode==null)
return accString;
else
{
var chd = curNode.GetChildByCode(hexPath.First.Value);
string meaning = (chd==null)? "not found": chd.meaning;
accString.Add(meaning);
hexPath.RemoveFirst();
return getMessagesByPath(hexPath,accString,chd);
}
}
// convert the code to a list of digits in the given base (in this case 16)
// this could be an extension method for int
private LinkedList<int> intToList(int theInt, int theBase, LinkedList<int> acc)
{
if(theInt < theBase)
{
acc.AddFirst(theInt);
return acc;
}
else
{
acc.AddFirst(theInt % theBase);
return intToList(theInt/theBase, theBase, acc);
}
}
}
you can populate the tree this way:
var root = new TreeNode(0,"root");
root.AddBranch(0x2121, new string[] {"Current", "DeviceInputSide", "Short to Earth", "In phase I"});
root.AddBranch(0x2122, new string[] {"Current", "DeviceInputSide", "Short to Earth", "In phase II"});
root.AddBranch(0x2123, new string[] {"Current", "DeviceInputSide", "Short to Earth", "In phase III"});
root.AddBranch(0x2221, new string[] {"Current", "DeviceOutputSide", "Short to Earth", "In phase I"});
root.AddBranch(0x2222, new string[] {"Current", "DeviceOutputSide", "Short to Earth", "In phase II"});
root.AddBranch(0x2223, new string[] {"Current", "DeviceOutputSide", "Short to Earth", "In phase III"});
// ...
this way you get total control over the hierarchical structure of your codes and can implement checks so that the structure itself cannot be corrupted. Searching a message remains easy and (since it does not process a code after the first 0), a search for 0x2000 should be more efficient because only the 2 is actually processed.
//search meaning of path
root.getMessagesByPath(0x2122)
Found that a modified version of Approach 3 is most suitable. Thanks to @paolo for helping me come up with the answer.
Modified Approach 3
The xml
containing the codes:
<?xml version="1.0" encoding="utf-8" ?>
<WarnCodes>
<code hex="2000" meaning="Current">
<code hex="2100" meaning="Current, Device Input side">
<code hex="2120" meaning="Short to Earth">
<code hex="2121" meaning="Short to earth in Phase L1"/>
<code hex="2122" meaning="Short to earth in Phase L2"/>
</code>
</code>
</code>
<code hex="3000" meaning="Voltage"/>
</WarnCodes>
The WarnCodeValue
class:
class WarnCodeValue
{
public string Meaning
{ get; set; }
public string ConcatenatedMeaning
{ get; set; }
public Dictionary<int, WarnCodeValue> ChildNodes
{ get; set; }
}
The singleton
processor class (to retrieve the meaning of a code):
sealed class WarnCodeProcessor
{
private static Dictionary<int, WarnCodeValue> warnCodesDictionary;
private static volatile WarnCodeProcessor _instance;
private static object instanceLockCheck = new object();
public static WarnCodeProcessor Instance
{
get
{
lock (instanceLockCheck)
{
if (_instance == null)
{
_instance = new WarnCodeProcessor();
}
}
return _instance;
}
}
private WarnCodeProcessor()
{
warnCodesDictionary = new Dictionary<int, WarnCodeValue>();
string currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string settingsFilePath = Path.Combine(currentDirectory, "WarnCodes.xml");
XElement rootElement = XElement.Load(settingsFilePath);
warnCodesDictionary = GetChildren(rootElement, string.Empty);
}
public string GetConcatenatedMeaning(int code)
{
string concatenatedMeaning = string.Empty;
int firstLevel = code & 0xF000;
int secondLevel = code & 0xFF00;
int thirdLevel = code & 0xFFF0;
if (warnCodesDictionary.ContainsKey(firstLevel))
{
concatenatedMeaning = warnCodesDictionary[firstLevel].ConcatenatedMeaning;
if (warnCodesDictionary[firstLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes.ContainsKey(secondLevel))
{
concatenatedMeaning =
warnCodesDictionary[firstLevel].
ChildNodes[secondLevel].ConcatenatedMeaning;
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes.ContainsKey(thirdLevel))
{
concatenatedMeaning =
warnCodesDictionary[firstLevel].
ChildNodes[secondLevel].
ChildNodes[thirdLevel].ConcatenatedMeaning;
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes.ContainsKey(code))
{
concatenatedMeaning =
warnCodesDictionary[firstLevel].
ChildNodes[secondLevel].
ChildNodes[thirdLevel].
ChildNodes[code].ConcatenatedMeaning;
}
}
}
}
return concatenatedMeaning;
}
private static Dictionary<int, WarnCodeValue> GetChildren(XElement element, string concatenatedMeaning)
{
string elementMeaning = string.Empty;
XAttribute attribute = element.Attribute("meaning");
if (attribute != null)
{
elementMeaning = attribute.Value;
concatenatedMeaning =
string.IsNullOrEmpty(concatenatedMeaning) ? elementMeaning : string.Format("{0} : {1}", concatenatedMeaning, elementMeaning);
}
if (element.Descendants().Count() > 0)
{
Dictionary<int, WarnCodeValue> childNodeDictionary = new Dictionary<int, WarnCodeValue>();
foreach (XElement childElement in element.Elements())
{
int hex = Convert.ToInt32(childElement.Attribute("hex").Value, 16);
string meaning = childElement.Attribute("meaning").Value;
Dictionary<int, WarnCodeValue> dictionary = GetChildren(childElement, concatenatedMeaning);
WarnCodeValue warnCodeValue = new WarnCodeValue();
warnCodeValue.ChildNodes = dictionary;
warnCodeValue.Meaning = meaning;
warnCodeValue.ConcatenatedMeaning =
string.IsNullOrEmpty(concatenatedMeaning) ? meaning : string.Format("{0} : {1}", concatenatedMeaning, meaning);
childNodeDictionary.Add(hex, warnCodeValue);
}
return childNodeDictionary;
}
return null;
}
}
Usage
string concatenatedMeaning = WarnCodeProcessor.Instance.GetConcatenatedMeaning(0x2121);
Output
Current : Current, Device Input side : Short to Earth : Short to earth in Phase L1
Possible modifications include a GetMeaning(code)
to retrieve the original meaning of the code, rather than the concatenated meaning.
精彩评论