Представление иерархического перечисления - PullRequest
8 голосов
/ 18 февраля 2011

У меня есть набор значений перечисления (точные коды ошибок).Код представляет собой 16-битное целое число без знака.Я ищу структуру данных, которая могла бы представлять такое перечисление.Аналогичный вопрос был задан здесь: Каков наилучший шаблон C # для реализации иерархии с перечислением? .Но эта иерархия глубже.


Примеры значений перечисления

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


Вариант использования
Когда пользователь предоставляет код, пользовательский интерфейс должен отображать эквивалентное значение кода с иерархией.
Например, если пользователь предоставляет значение 0x2121, пользовательский интерфейс должен отображать Short to earth in phase 1 in the current at device input side.Лучший способ представить это - использовать иерархическую запись: Current : DeviceInputSide : ShortToEarth : ShortToEarthInPhase1.


Конкурирующие подходы
У меня есть три конкурирующих подхода для представления перечисления:

  1. Создать перечисление на каждом уровне иерархии.Затем используйте класс контроллера для разрешения имени.
  2. Сохраните значения перечисления в xml и используйте LINQ для генерации значения кода.
  3. Сохраните значения перечисления в xml.Во время запуска приложения.Создайте единичный экземпляр, чтобы получить значение.Экземпляр содержит словарь, заполненный значениями из xml.


Подход 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 для хранения значений перечисления выглядит следующим образом

<code><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. Какой из трех вышеуказанных подходов лучше с точки зрения производительности?
  2. Существуют ли другие способы представления перечисления?
  3. Есть ли улучшения в коде?

Ответы [ 4 ]

4 голосов
/ 18 февраля 2011

Рассмотрим , используя классы вместо перечислений, затем вы используете одиночное значение для каждого значения и можете использовать систему типов для построения дерева, в том числе виртуальные методы для создания ошибки txt и т. Д. (Это может иногда может быть хорошим вариантом , но также может привести к множеству проблем , если он не подходит)

1 голос
/ 20 февраля 2011

Вторая попытка ... Вы можете реализовать свою собственную древовидную структуру, где каждый узел имеет шестнадцатеричное представление, состоящее из одной цифры, а код, такой как 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)
1 голос
/ 18 февраля 2011

Вы можете использовать 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( ) );
}
0 голосов
/ 21 февраля 2011

Обнаружено, что модифицированная версия подхода 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) для извлечения исходного значения кода, а не связанного значения.

...