Click here to Skip to main content
11,790,880 members (62,589 online)
Click here to Skip to main content

A Tree Collection

, 25 Feb 2005 CPOL 179.9K 5.3K 102
Rate this:
Please Sign up or sign in to vote.
An implementation of a Tree Collection in C#.


I needed a tree collection for something or other a while back. I couldn't find one at CP, so I decided to roll my own -- after all, how hard could it be? 2000 lines of code later... I hope you can gain from my experience, and at least use this code as a starting point. I included everything I could think of, including a verbose event set.


This code started life as a Composite pattern. However, I soon realized that I didn't need two classes as a node is a tree, and a tree is a node. So I rolled them into one class called NodeTree. This has probably been done before, but it just seemed like a good idea to me.

You can derive a class from NodeTree to provide specialization to a particular data type, but you don't have to. If you are happy to rely on runtime type checking and exceptions, then you can create a strongly-typed tree by providing the data type in the static constructor:

ITree tree = NodeTree.NewTree( typeof( Element ) );

Using the code

I wrote a test application to check my code as I wrote it. This grew to almost as long as the collection. It is the best resource to find out how I intended the collection to be used. I will elaborate on various aspects in the following sections:


Although only one class, NodeTree, is being used to model the tree, the consumer does not directly use it. They manipulate the collection through two interfaces: INode and ITree. The NodeTree class derives from both of these interfaces:

internal class NodeTree : INode, ITree, ISerializable

So a NodeTree is the internal representation of both a node and a tree. However, the two interfaces only declare members that make sense to that particular point of view. There is considerable equivalent behavior between the two interfaces; for example they both declare InsertChild methods. In general, the INode interface is the larger, although ITree does declare some unique members like Clear.


Each interface has a property that allows conversion to the other.

INode declares a property Tree, which gets a reference to the node's root node, as an ITree.

ITree declares a property Root, which gets a reference to the tree's root node, as an INode.


The purpose of a collection is to hold data. In this collection, the data is held in a private field, _Data, in the NodeTree class. Access to this object is through the INode interface, which declares a public property Data:

object Data { get; set; }

ITree does not declare this property, as it does not make sense, as we shall see later.

A lot of the members act on a data object rather than a node. For example, one InsertChild method is declared as:

INode InsertChild( object o )

It takes the data object as a parameter, creates a node wrapper, returns the new INode. It is actually an error to try to insert an INode, although you can insert a ITree, as we shall see later.


There are two ways to copy the subtree defined by a NodeTree: Copy and DeepCopy.

Copy creates new tree nodes, but sets the data property of each new node to reference the same instance of the data as the original node.

DeepCopy attempts to make a copy of the data as well. I have defined an interface IDeepCopy as:

internal interface IDeepCopy
    object CreateDeepCopy();

If the data object supports this interface, then CreateDeepCopy is called on the data from each node being copied.

If the data does not support this interface, then an attempt is made to instantiate a new object of the same type as the data object, using the copy constructor:

Activator.CreateInstance( data.GetType(), new object[] { data } );

If no copy constructor exists either, then DeepCopy gives up and just copies a reference to the data.


I make extensive use of exceptions to check for invalid operations. This reflects my preference (in this collection, anyway) for runtime type checking.

In particular, there are many places which throw an exception if you pass an unwrapped node rather than a tree.


There is a structural difference between a tree accessed through INode and one accessed through ITree. The ITree NodeTree has an extra node at the root, which has as its data object a RootObject object. This is a protected class nested in NodeTree, so you can't instantiate one. This is to make sure that exactly one node has a RootObject as its data, that is the root node of an ITree. It also proved a handy place to store the data type of the tree's data objects.

So there is an inherent difference between an INode and an ITree. If you try to pass an INode to methods that take an ITree, an exception will be raised. This is by design. Generally, you use ITree most of the time, and only use INode to reference a particular node that is contained in an ITree.

This sounds complicated, but actually is quite intuitive when you get used to it.

For example, if you want to move a node from one place in a tree to another, then you could use the following:

void Move( INode source, INode destination )
    ITree temp = source.Cut();
    destination.InsertChild( temp );

Note that the Cut method returns an ITree, not an INode. Similarly, the InsertChild method takes an ITree, not an INode.


I will now go through the interfaces in detail. Most of this is obvious, but it's worth skimming through or using as a reference.


These properties return an INode because they reference nodes within a tree:

INode Parent    { get; }
INode Previous  { get; }
INode Next      { get; }
INode Child     { get; }

INode Root      { get; }
INode Top       { get; }
INode First     { get; }
INode Last      { get; }

INode LastChild { get; }

The child property references the first child of a node.

In a tree, there is one root NodeTree, which is hidden from the user. The direct children of this root node are the "top" nodes.

Boolean properties

These properties get information about the structure around a node:

bool IsTree      { get; }
bool IsRoot      { get; }
bool IsTop       { get; }
bool HasParent   { get; }
bool HasPrevious { get; }
bool HasNext     { get; }
bool HasChild    { get; }
bool IsFirst     { get; }
bool IsLast      { get; }

The IsTree property checks for the existence of a RootObject as the node's data. If it is there, then it returns true.

The IsRoot property simply checks for the existence of a parent node.


These methods perform a linear search of the subtree below the current node, looking for a node with the specified data.

INode this[ object o ] { get; }

bool Contains( object o );

Insert objects

These methods create a wrapper NodeTree around the specified data object, insert this node into the tree, and return the new node:

INode InsertPrevious( object o );
INode InsertNext    ( object o );
INode InsertChild   ( object o );
INode Add           ( object o );
INode AddChild      ( object o );

Insert nodes

These methods just throw exceptions as it is illegal to add a node to a tree - you must add a new object, or a complete tree:

void InsertPrevious( INode node );
void InsertNext    ( INode node );
void InsertChild   ( INode node );
void Add           ( INode node );
void AddChild      ( INode node );

Insert trees

These methods insert the specified tree into this tree:

void InsertPrevious( ITree tree );
void InsertNext    ( ITree tree );
void InsertChild   ( ITree tree );
void Add           ( ITree tree );
void AddChild      ( ITree tree );

Cut, copy, remove nodes

These methods operate on nodes:

ITree Cut           ();
ITree Copy          ();
ITree DeepCopy      ();
void  Remove        ();

Cut, copy, remove data

These methods operate on the first node found that has the specified object as its data:

ITree Cut           ( object o );
ITree Copy          ( object o );
ITree DeepCopy      ( object o );
void  Remove        ( object o );

More boolean properties

These properties return values that indicate whether the corresponding methods are valid:

bool CanMoveToParent   { get; }
bool CanMoveToPrevious { get; }
bool CanMoveToNext     { get; }
bool CanMoveToChild    { get; }
bool CanMoveToFirst    { get; }
bool CanMoveToLast     { get; }


These methods perform common movements on the current node:

void MoveToParent   (); 
void MoveToPrevious (); 
void MoveToNext     (); 
void MoveToChild    (); 
void MoveToFirst    ();
void MoveToLast     ();


This method returns the number of direct children of a node:

int DirectChildCount { get; }


INode and ITree both derive from IValuesCollection, which is derived from ICollection, which is derived from IEnumerable, so they can both act as enumerators in themselves. I have added these four extra collections that operate on this NodeTree's children:

IValuesCollection Nodes                   { get; }
IValuesCollection AllChildren             { get; }
IValuesCollection DirectChildren          { get; }
IValuesCollection DirectChildrenInReverse { get; }
The Nodes collection is exactly the same as the DirectChildren collection.

The interface IValuesCollection just adds the Values property to ICollection. This means that your can access this property of all the collections to get a collection that returns the data in a node, not the node itself. This means that you can access the data objects directly with code like this :

foreach ( Element o in node.DirectChildren.Values )


This property is only declared in the ITree interface. It retrieves the data type of the data objects held in the tree:

Type DataType { get; }


This method is only declared in the ITree interface. It just empties a tree:

void Clear();


The following events are made available by the NodeTree class:

event NodeTreeDataEventHandler     Validate;
event EventHandler                 Clearing;
event EventHandler                 Cleared;
event NodeTreeDataEventHandler     Setting;
event NodeTreeDataEventHandler     SetDone;
event NodeTreeInsertEventHandler   Inserting;
event NodeTreeInsertEventHandler   Inserted;
event EventHandler                 Cutting;
event EventHandler                 CutDone;
event NodeTreeNodeEventHandler     Copying;
event NodeTreeNodeEventHandler     Copied;
event NodeTreeNodeEventHandler     DeepCopying;
event NodeTreeNodeEventHandler     DeepCopied;

You can attach to an event at the node or tree level. Every event is raised for the current node, and then for the containing tree. I thought about raising the events for each parent of the current node, but this seemed a bit too much.

The user will probably only ever attach to the Validate event of a tree, but the option is there to attach to every event at every node.

The default Validate handler checks the type of the data object, and throws an exception if this does not match the type set in the tree's constructor.

See "Points of Interest" which explains about using an EventHandlerList object to minimize the space overhead of so many events.


I have added serialization support by implementing ISerializable. This is useful if you want to persist a tree, or if you want to put a tree onto the clipboard.

I have added support for XmlSerialization in version 4.


Both interfaces derive from ICollection, and thus also from IEnumerable.

Points of Interest


Note the use of ISerializable, and the persistence of a Version number to help future-proof the serialization process.

The default serialization implementation is inflexible, but these two operations go a long way to mitigating its failures.

I wrote a couple of Adapter classes to support XmlSerialization - see the bottom of Form1.cs.

You can select which formatter to use by moving the commented lines in the handlers for the Serialization and Deserialization buttons.


I have made a lot of events available - probably more than anyone will ever need outside of a test application. This would have had an unacceptable increase in the space requirements of the NodeTree class, so I used an EventHandlerList object to minimize the impact. Basically, instead of having a collection for each event in a class, you only have one collection for all events, and use key objects to only record events that are attached. Thus, each instance of NodeTree just has one instance of EventHandlerList, and this only records attached events. This makes the raising an event a little more complicated, but not very much so.


This collection is not meant to be a panacea. It favors functionality over efficiency, which has made it quite fat. However, it does fill a gap in the .NET Framework, and is certainly better than using an invisible TreeView. I present it here as another option to add to your toolbox.


  • 2005 February 25 : Version 5 : Upgraded all enumerations to collections. See Collections.
  • 2004 June 7 : Version 4 : Added support for XmlSerialization
  • 2004 April 19 : Version 3 : Enumerations now throw exceptions properly.
  • 2004 March 30 : Version 2 : Fixed naming conventions.
  • 2004 March 30 : Version 1.


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


About the Author

Nicholas Butler
United Kingdom United Kingdom

I built my first computer, a Sinclair ZX80, on my 11th birthday in 1980.
In 1992, I completed my Computer Science degree and built my first PC.
I discovered C# and .NET 1.0 Beta 1 in late 2000 and loved them immediately.
I have been writing concurrent software professionally, using multi-processor machines, since 1995.

In real life, I have spent 3 years travelling abroad,
I have held a UK Private Pilots Licence for 20 years,
and I am a PADI Divemaster.

I now live near idyllic Bournemouth in England.

If you would like help with multithreading, please contact me via my website:

I can work 'virtually' anywhere!

You may also be interested in...

Comments and Discussions

GeneralExcellent! Pin
Michael Gunlock3-Mar-08 15:06
memberMichael Gunlock3-Mar-08 15:06 
GeneralThis is great!! Pin
screig1-Oct-07 2:56
memberscreig1-Oct-07 2:56 
GeneralIndexer Pin
Spass200330-Nov-06 4:24
memberSpass200330-Nov-06 4:24 
GeneralRe: Indexer Pin
Spass20031-Dec-06 1:30
memberSpass20031-Dec-06 1:30 
GeneralCreate a dll Pin
123456uio9-Aug-06 0:16
member123456uio9-Aug-06 0:16 
QuestionInputting user data Pin
ann_smith18-Jul-06 11:34
memberann_smith18-Jul-06 11:34 
GeneralDoesn't work with webservices Pin
SpArtA10-Apr-06 3:54
memberSpArtA10-Apr-06 3:54 
AnswerYes it does Pin
Nicholas Butler10-Apr-06 5:00
memberNicholas Butler10-Apr-06 5:00 
GeneralRe: Yes it does Pin
SpArtA10-Apr-06 21:54
memberSpArtA10-Apr-06 21:54 
GeneralRe: Yes it does Pin
SpArtA10-Apr-06 22:42
memberSpArtA10-Apr-06 22:42 
AnswerRe: Yes it does Pin
Nicholas Butler11-Apr-06 0:03
memberNicholas Butler11-Apr-06 0:03 
GeneralYeah Pin
SpArtA12-Apr-06 3:38
memberSpArtA12-Apr-06 3:38 
GeneralRe: Yeah Pin
Nicholas Butler12-Apr-06 3:55
memberNicholas Butler12-Apr-06 3:55 
GeneralAssociating data to root node Pin
nagarsoft28-May-05 7:39
membernagarsoft28-May-05 7:39 
GeneralRe: Associating data to root node Pin
Nicholas Butler28-May-05 11:10
memberNicholas Butler28-May-05 11:10 
GeneralPlease remove windows.forms dependencies Pin
v1nce_fr29-Mar-05 23:03
memberv1nce_fr29-Mar-05 23:03 
GeneralRe: Please remove windows.forms dependencies Pin
AntVampo11-Oct-05 11:17
memberAntVampo11-Oct-05 11:17 
GeneralExtra usings Pin
tomas.tintera4-Mar-05 0:31
membertomas.tintera4-Mar-05 0:31 
GeneralRe: Extra usings Pin
Nicholas Butler4-Mar-05 3:22
memberNicholas Butler4-Mar-05 3:22 
GeneralComposite Pattern Pin
Ray Hayes27-Feb-05 7:32
memberRay Hayes27-Feb-05 7:32 
GeneralRe: Composite Pattern Pin
Nicholas Butler4-Mar-05 3:17
memberNicholas Butler4-Mar-05 3:17 
GeneralRe: Composite Pattern Pin
Ray Hayes4-Mar-05 3:26
memberRay Hayes4-Mar-05 3:26 
GeneralRe: Composite Pattern Pin
Leblanc Meneses19-Sep-08 19:56
memberLeblanc Meneses19-Sep-08 19:56 
QuestionWhat happened to NCollection? Pin
crowcomputer20-Jan-05 12:18
membercrowcomputer20-Jan-05 12:18 
Generaltree.xml Pin
SilentHand17-Sep-04 1:14
sussSilentHand17-Sep-04 1:14 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1509028.1 | Last Updated 26 Feb 2005
Article Copyright 2004 by Nicholas Butler
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid