Click here to Skip to main content
15,860,859 members
Articles / Programming Languages / C#
Article

Tree Structured Enumerations

Rate me:
Please Sign up or sign in to vote.
3.32/5 (6 votes)
2 Apr 2008CPOL3 min read 74.3K   232   15   32
The way to maintain a tree structured enumeration while having all the advantages of the standard ones

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.

C#
[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.

C#
[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.

C#
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.

C#
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.

C#
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)


Written By
Software Developer
Czech Republic Czech Republic
Contacts: EMAIL - smartk8@gmail.com

Comments and Discussions

 
Questionnot used attribute Pin
Member 1023970413-Feb-17 23:04
Member 1023970413-Feb-17 23:04 
GeneralI did it my way (Part 1) [modified] Pin
PIEBALDconsult10-Apr-08 17:22
mvePIEBALDconsult10-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
    ,
        AllowMultiple=false
    ,
        Inherited=false
    )]
    public sealed class EnumTreeAttribute : System.Attribute
    {
        private static readonly System.Collections.Generic.Dictionary
        <
            System.Type
        ,
            object
        > masks = new System.Collections.Generic.Dictionary
        <
            System.Type
        ,
            object
        >( 1 ) ;
        
/**************************************************************************************************************/
        
        /**
        <summary>
            Retrieve the masks for the enum.
        </summary>
        <returns>
            A List&lt;T&gt; of the members of the enum that are marked with the attribute.
        </returns>
        <exception cref="System.ArgumentException">
            If the supplied type is not an enum.
        </exception>
        */
        public static System.Collections.ObjectModel.ReadOnlyCollection<T>
        Masks<T>
        (
        )
        {
            System.Collections.ObjectModel.ReadOnlyCollection<T> result ;
            
            lock ( masks )
            {
                System.Type thetype = typeof(T) ;
            
                if ( masks.ContainsKey ( thetype ) )
                {
                    result = (System.Collections.ObjectModel.ReadOnlyCollection<T> ) masks [ thetype ] ;
                }
                else
                {
                    if ( !thetype.IsEnum )
                    {
                        throw ( new System.ArgumentException ( "T must be an Enum" ) ) ;
                    }
 
                    System.Collections.Generic.List<T> temp = new System.Collections.Generic.List<T>() ;
                    
                    foreach ( System.Reflection.FieldInfo val in thetype.GetFields() )
                    {
                        if ( val.GetCustomAttributes
                        (
                            typeof(EnumTreeAttribute)
                        ,
                            false
                        ).Length > 0 )
                        {
                            temp.Add ( (T) val.GetValue ( null ) ) ;
                        }
                    }
 
                    temp.TrimExcess() ;
                    
                    temp.Sort() ;
                    
                    masks [ thetype ] = result = temp.AsReadOnly() ;
                }
            }
        
/**************************************************************************************************************/
    }
}


Use it like:

public enum AnimalKind
{
    [PIEBALD.Attributes.EnumTreeAttribute()]
    ClassMask       = 0xF000,

    [PIEBALD.Attributes.EnumTreeAttribute()]
    SubclassMask    = 0x0F00,

    [PIEBALD.Attributes.EnumTreeAttribute()]
    MemberMask      = 0x00FF,

    DomesticAnimals = 0x1000,
      Dog           = 0x1100,
        Dalmation   = 0x1101,
        Greyhound   = 0x1102,
        Malamute    = 0x1103,
        Terrier     = 0x1104,
      Cat           = 0x1200,

    // Another technique
    WildAnimals     =               0x2000,
      Ape           = WildAnimals | 0x0100,
        Chimpanzee  = Ape         | 0x0001,
        Gorilla     = Ape         | 0x0002,
        Orangutan   = Ape         | 0x0003,
      Deer          = WildAnimals | 0x0200,
        MuleDeer    = Deer        | 0x0001,
        WhiteTailed = Deer        | 0x0002
}


(I'm still working on the helper class that uses it, I keep changing my mind on the details.)

Edit: Such as making the List a ReadOnlyCollection.

modified on Thursday, April 10, 2008 11:53 PM

GeneralRe: I did it my way (Part 1) P.S. Pin
PIEBALDconsult24-Apr-08 9:52
mvePIEBALDconsult24-Apr-08 9:52 
GeneralOh, and... Pin
PIEBALDconsult10-Apr-08 15:40
mvePIEBALDconsult10-Apr-08 15:40 
GeneralC# 3.0 extension methods version Pin
Smart K89-Apr-08 2:15
professionalSmart K89-Apr-08 2:15 
GeneralRe: C# 3.0 extension methods version Pin
PIEBALDconsult10-Apr-08 14:24
mvePIEBALDconsult10-Apr-08 14:24 
GeneralClarify presentation Pin
Seth Morris8-Apr-08 13:33
Seth Morris8-Apr-08 13:33 
AnswerRe: Clarify presentation [modified] Pin
Smart K89-Apr-08 0:40
professionalSmart K89-Apr-08 0:40 
GeneralSuggestions [modified] Pin
PIEBALDconsult6-Apr-08 17:20
mvePIEBALDconsult6-Apr-08 17:20 
GeneralSuggestions Part 2 Pin
PIEBALDconsult6-Apr-08 18:11
mvePIEBALDconsult6-Apr-08 18:11 
GeneralRe: Suggestions Part 2 Pin
Smart K86-Apr-08 19:43
professionalSmart K86-Apr-08 19:43 
GeneralRe: Suggestions Part 2 Pin
PIEBALDconsult7-Apr-08 13:42
mvePIEBALDconsult7-Apr-08 13:42 
GeneralRe: Suggestions Part 2 Pin
Smart K87-Apr-08 20:43
professionalSmart K87-Apr-08 20:43 
GeneralToo complex Pin
PIEBALDconsult2-Apr-08 10:54
mvePIEBALDconsult2-Apr-08 10:54 
GeneralP.S. Too complex Pin
PIEBALDconsult2-Apr-08 11:09
mvePIEBALDconsult2-Apr-08 11:09 
GeneralRe: P.S. Too complex Pin
Smart K82-Apr-08 11:31
professionalSmart K82-Apr-08 11:31 
GeneralRe: P.S. Too complex Pin
PIEBALDconsult2-Apr-08 11:35
mvePIEBALDconsult2-Apr-08 11:35 
GeneralRe: P.S. Too complex Pin
Smart K82-Apr-08 11:44
professionalSmart K82-Apr-08 11:44 
GeneralRe: P.S. Too complex Pin
PIEBALDconsult2-Apr-08 12:42
mvePIEBALDconsult2-Apr-08 12:42 
AnswerRe: P.S. Too complex [modified] Pin
Smart K82-Apr-08 19:29
professionalSmart K82-Apr-08 19:29 
GeneralRe: P.S. Too complex Pin
PIEBALDconsult2-Apr-08 14:26
mvePIEBALDconsult2-Apr-08 14:26 
GeneralReinventing the wheel Pin
leppie2-Apr-08 10:17
leppie2-Apr-08 10:17 
AnswerRe: Reinventing the wheel Pin
Smart K82-Apr-08 10:35
professionalSmart K82-Apr-08 10:35 
GeneralRe: Reinventing the wheel Pin
leppie2-Apr-08 11:10
leppie2-Apr-08 11:10 
GeneralRe: Reinventing the wheel Pin
Smart K82-Apr-08 11:35
professionalSmart K82-Apr-08 11:35 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.