表示分层枚举

时间:2011-02-18 12:18:45

标签: c# .net enumeration

我有一组枚举值(准确的故障代码)。代码是16位无符号整数。我正在寻找一个可以代表这种枚举的数据结构。这里也提出了类似的问题:What's the best C# pattern for implementing a hierarchy with an enum?。但这种等级制度更深。


样本枚举值

Current = 0x2000,
Current_DeviceInputSide = 0x2100,
ShortToEarth = 0x2120,
ShortToEarthInPhase1 = 0x2121,
ShortToEarthInPhase2 = 0x2122,
ShortToEarthInPhase3 = 0x2123


用例
当用户提供代码时,UI必须显示具有层次结构的代码的等效含义。
例如,如果用户提供值0x2121,则UI必须显示Short to earth in phase 1 in the current at device input side。表示此问题的最佳方式是使用分层表示法:Current : DeviceInputSide : ShortToEarth : ShortToEarthInPhase1


竞争方法
我有三种竞争方法来表示枚举:

  1. 在层次结构的每个级别创建枚举。然后使用控制器类来解析名称。
  2. 将枚举值存储在xml中,并使用LINQ生成代码的含义。
  3. 将枚举值存储在xml中。在应用程序启动期间。创建单例实例以检索含义。该实例包含一个填充了xml值的字典。

  4. 方法1
    枚举:

    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
    }
    

    控制器:

    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;
    
                ...
        }
    }
    


    方法2
    存储枚举值的xml如下所示

    <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>
    
    用于查询代码的方法是:

    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();
    }
    


    方法3
    存储枚举值的xml与方法2 中的相同。字典由xml 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, warnCodeValue);
            }
    
            return childNodeDictionary;
        }
    
        return null;
    }
    

    使用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);
                    }
                }
            }
        }
    }
    

    WarnCodeValue类:

    class WarnCodeValue
    {
        public string Meaning
        { get; set; }
    
        public Dictionary<int, WarnCodeValue> ChildNodes { get; set; }
    }
    


    问题

    1. 从性能的角度来看,上述3种方法中哪一项更好?
    2. 是否还有其他方法来表示枚举?
    3. 对代码的任何改进?

4 个答案:

答案 0 :(得分:4)

考虑使用类而不是枚举,然后为每个值使用单例,并且可以使用类型系统构建树,包括生成错误txt的虚拟方法等。(这可以有时候是一个不错的选择,但如果不合适的话,也会引导你进入很多问题

答案 1 :(得分:1)

您可以使用FlagsAttribute。 例如,你可以这样做:

[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
}       

你可以这样测试:

int[] testVals = {0x0000, 0x2000, 0x2130, 0x2122, 0x2121, 0x2131};

foreach(var val in testVals)
{
   Console.WriteLine( "{0,4:X} - {1}",
      val, ( (WarnCodes)val ).ToString( ) );
}

答案 2 :(得分:1)

第二次尝试......您可以实现自己的树结构,其中每个节点都有一位十六进制表示,而像0x2121这样的代码代表树的一个分支:

                      >2 - (current)
                      / \
 (device input side)>1   2 (device output side)
                    /\   /\
                     >2 (short to earth)
                     /\  
                   >1 (in phase 1) 

因此,要读取0x2121的含义,我们将遵循树的相应分支,并且(对于每个节点)我们读取它包含的消息。

这是一个快速而又脏的树实现:

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);
        }
    }
}

你可以这样填充树:

        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"});
// ...
通过这种方式,您可以完全控制代码的层次结构,并且可以实现检查,以便结构本身不会被破坏。搜索消息仍然很容易(因为它在第一个0之后不处理代码),搜索0x2000应该更有效,因为实际上只处理了2个。

//search meaning of path
root.getMessagesByPath(0x2122)

答案 3 :(得分:0)

发现方法3 的修改版本是最合适的。感谢 @paolo 帮助我找到答案。

修改方法3

包含代码的xml

<?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>


WarnCodeValue类:

class WarnCodeValue
{
    public string Meaning
    { get; set; }

    public string ConcatenatedMeaning
    { get; set; }

    public Dictionary<int, WarnCodeValue> ChildNodes 
    { get; set; }
}


singleton处理器类(用于检索代码的含义):

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;
    }
}


用法

string concatenatedMeaning = WarnCodeProcessor.Instance.GetConcatenatedMeaning(0x2121);


输出
Current : Current, Device Input side : Short to Earth : Short to earth in Phase L1


可能的修改包括GetMeaning(code)来检索代码的原始含义,而不是连接的含义。