开发者

Representing heirarchical enumeration

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 values

Current = 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:

  1. Create an enumeration at each level of the hierarchy. Then use a controller class to resolve the name.
  2. Store the enumeration values in an xml and use LINQ to generate the meaning of the code.
  3. 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.

Approach 1

The enumerations:

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

  1. Which of the above 3 approaches is better from a performance point of view?
  2. Are there any other approaches for representing the enumeration?
  3. 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜