Click here to Skip to main content
Click here to Skip to main content

Tree Structured Enumerations

By , 2 Apr 2008
 

Introduction

In this article, I want to show you an approach of how a structured enumeration can be handled by C#.

Background

While playing with my little home project, I stumbled upon a problem of having all those category enumerations displayed in a tree. I wanted to keep it simple - as the enumerations are - while avoiding the need to create structured object hierarchies for every one of them. So, after a bit of thinking, I came up with this solution. I hope you'll find it useful or at least interesting.

Structured Enumeration

First, we have to tickle our old plain list enumeration a bit and convert it to a structured one. I chose the animal categories.. well.. to confess, I'm always having slight difficulties to find a good example, but here it is anyway.

[Structured(100)]
public enum AnimalKind
{
    Unknown         = 00000,

    DomesticAnimals = 00001,
      Dog           = 00100,
        Dalmatin    = 10000,
        Greyhound   = 10001,
        Malamute    = 10002,
        Terrier     = 10003,
      Cat           = 00101,
        
    WildAnimals     = 00002,
      Ape           = 00200,
        Chimpanzee  = 20000,
        Gorrila     = 20001,
        Orangutan   = 20002,
      Deer          = 00201
}

As you might notice, some unknown attribute is used there. Let me introduce it to you.

[AttributeUsage(AttributeTargets.Enum)]
public class StructuredAttribute : Attribute
{
    public int span;

    public int Span
    {
        get { return span; }
        set { span = value; }
    }

    public StructuredAttribute(int span)
    {
        this.span = span;
    }
}

This simple attribute is composed of only one property (an automatic property may be used in C# 3.0, God I love those). The attribute is responsible for determining the span (multiplicator) of the tree levels thus allowing us to distinguish the hierarchy later.

Well.. later is now because this ought to be a short article. I took the liberty to create the utility class to help us deal with the structured enumerations. It consists of two static methods. Let's take a closer look at them.

Methods

The IsChild method is used to determine if one enumeration value is placed under another one. I guess the utility can be easily extended by a method determining the whole chain of the parents from a particular enum value.

public static bool IsChild<ttype>(TType child, TType parent)
{
    Type enumType = typeof(TType);
    Object[] attributeList = enumType.GetCustomAttributes
        (typeof(StructuredAttribute), true);

    if (attributeList.Length > 0)
    {
        StructuredAttribute attribute = (StructuredAttribute)attributeList[0];
        int span = attribute.Span;
        int parentIndex = (int)Convert.ChangeType(parent, typeof(int));
        int childIndex = (int)Convert.ChangeType(child, typeof(int));
        int index = childIndex / span;
        return (index == parentIndex && childIndex != parentIndex);
    }

    return true;
}

Another useful method that may be of interest to us is the CreateList method. It obviously creates a list of the child enumeration values under a particular parental value. It will also allow us to use the output list for the display or various cycle purposes. I can imagine an iterator here.

public static List<ttype> CreateList<ttype>(TType parent)
{
    List<ttype> result = new List<ttype>();
    Type enumType = typeof (TType);
    TType[] enumValues = (TType[]) Enum.GetValues(enumType);
    
    foreach (TType enumValue in enumValues)
    {
        if (IsChild(enumValue, parent))
        {
            result.Add(enumValue);
        }
    }

    return result;
}

Some Examples of Use

This example took all I mentioned above and put it to use. It will dump the tree to a console output while making the levels indented.

private static void DumpTree(AnimalKind parent, Int32 level)
{
    foreach (AnimalKind animalKind in EnumUtility.CreateList(parent))
    {
        string caption = animalKind.ToString();
        int width = caption.Length;
        string output = caption.PadLeft(width + level, ' ');
        Console.WriteLine(output);
        DumpTree(animalKind, level + 1);
    }
}

static void Main()
{
    DumpTree(AnimalKind.Unknown, 0);
    Console.ReadKey();
}

Possible Enhancements

  1. As I said earlier in this article, I can imagine some kind of iterator (or perhaps an indexer) instead of the CreateList method.
  2. The default indexing capabilities of enumerations can be widened by "inheriting" the enumeration from ulong type instead of default uint.
  3. The utility can also be extended with any kind of structuring routine which suits your need such as retrieving the chain of parents for a particular value.

Limitations

  • The complex trees with many levels may find their limit because the indexing will reach the limit of enumeration (ulong). This limitation can be reduced by lowering the span value on the attribute, thus allowing to scale for the count of branches against the count of levels.
  • It is recommended to use some default (zero) value which will then be used to retrieve the level one branches.

Personal Note

I found these structured enumerations quite useful myself dealing with countless - now waiting to be structured - category enumerations. They cut the time needed to create the editable trees where the categories are distinguished from the instance items. Moreover, the enumeration is still one type in the end.

Good luck and I will be pleased to hear your comments.

History

  • 2008-04-02: Missing example was added (Shall I ever get it right the first time?)
  • 2008-04-02: Initial article posted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Smart K8
Software Developer TelPro spol. s r.o.
Czech Republic Czech Republic
Member
Contacts: EMAIL - smartk8@gmail.com

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralI did it my way (Part 1) [modified]memberPIEBALDconsult10 Apr '08 - 17:22 
Here's my take on the attribute.   namespace PIEBALD.Attributes { /** <summary> An attribute to aid in applying a tree structure to an enum. </summary> */ [System.AttributeUsageAttribute ( System.AttributeTargets.Field , ...
GeneralRe: I did it my way (Part 1) P.S.memberPIEBALDconsult24 Apr '08 - 9:52 
public enum AnimalKind { [PIEBALD.Attributes.EnumTreeAttribute()] ClassMask = 0xF000 , [PIEBALD.Attributes.EnumTreeAttribute()] SubclassMask = ClassMask | 0x0F00 , [PIEBALD.Attributes.EnumTreeAttribute()] MemberMask = SubclassMask | 0x00FF...
GeneralOh, and...memberPIEBALDconsult10 Apr '08 - 15:40 
The Attribute probably shouldn't allow negative values.
GeneralC# 3.0 extension methods versionmemberSmart K89 Apr '08 - 2:15 
I've made the C# 3.0 enabled version. It is basically just a slightly modified conversion. Some suggestions from the local discussion were included. public static class EnumExtension { public static Boolean IsChildOf(this Enum child, Enum parent) { foreach (var...
GeneralRe: C# 3.0 extension methods versionmemberPIEBALDconsult10 Apr '08 - 14:24 
You're still hitting Reflection to get the Attributes on each call. This is very costly and unnecessary. They don't change, the values don't change; read them once and cache them, then only hit the cache.
GeneralClarify presentationmemberSeth Morris8 Apr '08 - 13:33 
Regardless of the active discussion of the technique (I like the overall idea a lot, personally), it took me a careful reading to "get" the meaning of the 100 value in the attribute.   A line or two of explanation might be a good addition. Maybe: Enums with the Structured attribute can...
AnswerRe: Clarify presentation [modified]memberSmart K89 Apr '08 - 0:40 
Hi, good ideas, typically I'm just emitting thoughts I don't care much about the final implementation . I let the reader decide which implementation suits him. Mostly I don't have time to polish them to the extreme knowing that is a waste of my time. Because no matter how sofisticated you'll...
GeneralSuggestions [modified]memberPIEBALDconsult6 Apr '08 - 17:20 
In your Attribute, I suggest that the field and/or property should not be publicly modifiable.   [AttributeUsage(AttributeTargets.Enum)] public class StructuredAttribute : Attribute { public int span; public int Span { get { return span;...
GeneralSuggestions Part 2memberPIEBALDconsult6 Apr '08 - 18:11 
I didn't really want to tear into this, but...   public static bool IsChild<ttype>(TType child, TType parent) <big>// Where if ttype used?</big> { Type enumType = typeof(TType); <big>// This is needless</big> Object[] attributeList =...
GeneralRe: Suggestions Part 2memberSmart K86 Apr '08 - 19:43 
PIEBALDconsult wrote:In your Attribute, I suggest that the field and/or property should not be publicly modifiable.   I missed that. I've got much bigger class EnumUtility I've just stripped away all the code non-concerned in the article. So the compromise: private int span = 10; // or...
GeneralRe: Suggestions Part 2memberPIEBALDconsult7 Apr '08 - 13:42 
Smart K8 wrote:I've just stripped away all the code non-concerned in the article   And didn't test it?     I meant, in the line:   public static bool IsChild<ttype>(TType child, TType parent)   where is ttype used? Is it supposed to be TType and you...
GeneralRe: Suggestions Part 2memberSmart K87 Apr '08 - 20:43 
PIEBALDconsult wrote:Smart K8 wrote: I've just stripped away all the code non-concerned in the article   And didn't test it?   Sure I did.   PIEBALDconsult wrote: I meant, in the line:   public static bool IsChild(TType child, TType parent)   where is ttype...
GeneralToo complexmemberPIEBALDconsult2 Apr '08 - 10:54 
Why did you choose to shift the higher-order sub-values up?   I would leave them in place (at the high-end), leave low-order fields as 0 to indicate "class", and add (OR) detail as I go. The "class" parts could also be at the low-end, but I think high-end is clearer. (Hexadecimal...
GeneralP.S. Too complexmemberPIEBALDconsult2 Apr '08 - 11:09 
This is what I mean:   namespace Template { public partial class Template { public enum AnimalKind { Unknown = 0x000000, Class = 0xFF0000, Subclass = 0xFFFF00,   DomesticAnimals =...
GeneralRe: P.S. Too complexmemberSmart K82 Apr '08 - 11:31 
Hi.. What you are describing is simple the [Flags] attribute.   The point is in the combination of scalability (branch count/level count) and the ability to list all the children because of that simple fact. Consider this question:   How do you construct a tree with three (or more)...
GeneralRe: P.S. Too complexmemberPIEBALDconsult2 Apr '08 - 11:35 
Smart K8 wrote:What you are describing is simple the [Flags] attribute.   Not at all.   Smart K8 wrote:Do you see the logic now ?   Just as I saw it before, but I don't agree that it's the best course of action.
GeneralRe: P.S. Too complexmemberSmart K82 Apr '08 - 11:44 
Sorry, I got confused with another thread.. I guess I'm getting tired.. It's 23:37 here. I'm going to sleep. I checked your solution and this is ofcourse possible but consider the scenario where the Span (as I call it) won't be 0xFF but something lower than that (for example 0xC) because the...
GeneralRe: P.S. Too complexmemberPIEBALDconsult2 Apr '08 - 12:42 
Smart K8 wrote:consider the scenario where the Span...   I was simply following your lead of using two digits. But what if you want different spans at different levels? For instance one-digit at the top-most level and two-digits at the next level down?   And, no, I haven't worked...
AnswerRe: P.S. Too complex [modified]memberSmart K82 Apr '08 - 19:29 
You're saying my numbers are unnatural?   IMHO.. in the end it is not important if you choose decimals, hexadecimals or any other representation of the value. I guess it's up to reader to make it suitable. I just thought that using decimals numbers as an example will be understable to a...
GeneralRe: P.S. Too complexmemberPIEBALDconsult2 Apr '08 - 14:26 
And thinking bit-wise allows more granularity of the class sizes. Yours only allows 10, 100, 1000, etc. members per class; but mine allows 3, 7, 15, 31, etc.
GeneralReinventing the wheelmember leppie 2 Apr '08 - 10:17 
What you are looking for is bit masks, and that is built into enum in C# (and many other languages) already.   [edit] you article is by no means bad [edit]   xacc.ide - now with IronScheme support IronScheme - 1.0 alpha 2 out now
AnswerRe: Reinventing the wheelmemberSmart K82 Apr '08 - 10:35 
Hi, I'm aware of the FlagsAttribute but if you give it another round I hope you'll notice the slight improvements which are important in this case.   regards, Kate
GeneralRe: Reinventing the wheelmember leppie 2 Apr '08 - 11:10 
Sorry I dont see it You can do the same with enums. You dont even need the Flags attribute. Here is a number hierarchy: <font color="Blue">enum</font> <font color="Teal">NumberClass</font> <font color="DarkBlue">{</font> Complex <font color="DarkBlue">=</font>...
GeneralRe: Reinventing the wheelmemberSmart K82 Apr '08 - 11:35 
Ok.. So how about a second branch in the same enum ?   regards, Kate   The wisdom is to see things truthfully.
GeneralRe: Reinventing the wheelmemberpeterchen2 Apr '08 - 19:30 
enum { Unknown = 0, Dalmatin = 1, Greyhound = 2, Malamute = 3, Terrier = 4,   DogMask = 0x0F, // room for 15 dogs CatMask = 0xF0, // ..and 15 cats DomesticMask = 0xFF,   Chimp = 0x0100, Gorilla = 0x0200, Orange = 0x0400 // I :rose: orange ApeMask =...

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 2 Apr 2008
Article Copyright 2008 by Smart K8
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid