表示层次枚举
我有一组枚举值(准确地说是故障代码)。该代码是一个 16 位无符号整数。我正在寻找一种可以表示此类枚举的数据结构。这里提出了类似的问题: 使用枚举实现层次结构的最佳 C# 模式是什么?。但这个层次结构更深。
示例枚举值
Current = 0x2000,
Current_DeviceInputSide = 0x2100,
ShortToEarth = 0x2120,
ShortToEarthInPhase1 = 0x2121,
ShortToEarthInPhase2 = 0x2122,
ShortToEarthInPhase3 = 0x2123
用例
当用户提供代码时,UI 必须显示该代码与层次结构的等效含义。
例如,如果用户提供值 0x2121
,则 UI 必须显示设备输入侧电流第 1 阶段对地短路
。表示这一点的最佳方法是使用分层表示法:Current : DeviceInputSide : ShortToEarth : ShortToEarthInPhase1
。
竞争方法
我有三种相互竞争的方法来表示枚举:
- 在层次结构的每个级别创建一个枚举。然后使用控制器类来解析名称。
- 将枚举值存储在 xml 中并使用 LINQ 生成代码的含义。
- 将枚举值存储在 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 如下所示
<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();
}
方法 3
存储枚举值的 xml 与方法 2 中的相同。该字典由 GetChildren()
从 xml
填充。
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; }
}
问题
- 从性能角度来看,上述 3 种方法中哪一种更好?
- 还有其他方法来表示枚举吗?
- 代码有什么改进吗?
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:
- 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.
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, warnCodeValue);
}
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
考虑使用类而不是枚举,然后为每个值使用单例,并可以使用类型系统构建树,包括生成错误txt等的虚拟方法。(这有时可以是一个不错的选择,但如果不合适也会导致你很多问题)
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)
您可以使用 FlagsAttribute。
例如你可以这样做:
你可以这样测试它:
You could use FlagsAttribute.
For instance you could do something like this:
You can test it like this:
第二次尝试...您可以实现自己的树结构,其中每个节点都有一位十六进制表示形式,并且像 0x2121 这样的代码表示树的分支:
因此,要读取 0x2121 的含义,我们遵循树的相应分支并且(对于每个节点)我们读取它包含的消息。
这是树的快速而肮脏的实现:
您可以通过这种方式填充树:
这样您就可以完全控制代码的层次结构,并可以实现检查,以便结构本身不会被损坏。搜索消息仍然很容易(因为它不处理第一个 0 之后的代码),搜索 0x2000 应该更有效,因为实际上只处理了 2。
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:
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:
you can populate the tree this way:
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.
发现方法3的修改版本最合适。感谢@paolo帮助我找到答案。
修改方法 3
包含代码的
xml
:WarnCodeValue
类:singleton
处理器类(用于检索代码的含义):用法
输出
电流:电流,设备输入侧:对地短路:L1 相对地短路
可能的修改包括使用
GetMeaning(code)
来检索代码的原始含义,而不是串联的含义。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:The
WarnCodeValue
class:The
singleton
processor class (to retrieve the meaning of a code):Usage
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.