|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
ContentsIntroductionI wrote a tree collection for .NET 1.1 last year - A Tree Collection [^]. This has proved to be quite popular, so I decided to write one for .NET 2.0 using generics. I reused a lot of code, but I had to write a lot of new interfaces from scratch, especially the enumerators. Once again, I don't know why MS didn't include an implementation of such a basic collection in the framework. This implementation fills that gap and is useable straight out of the box. I recommend reading the Structure section first, but if you just want to use this code, then look at the Quickstart section. StructureThis 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 InterfacesAlthough only one class, partial class NodeTree<T> : ITree<T>, INode<T>
So, a DataThe purpose of a collection is to hold data. In this collection, the data is held in a private field, partial interface INode<T>
{
T Data { get; set; }
}
Node structureEach node has the following structure:
Figure 1: Node structure Tree structureNodes link together to form a tree with the following structure:
Figure 2: Tree structure TerminologyThe QuickstartThis section will get you going in the shortest possible time. The examples show a tree with a data type of Firstly, you must create your tree: ITree<Element> tree = NodeTree<Element>.NewTree();
Next, create a top node: INode<Element> top = tree.AddChild( new Element() );
Note that you add instances of your data type and a node is created and inserted into the tree for you. Then add leaf nodes: INode<Element> leaf1 = top.AddChild(new Element());
INode<Element> leaf2 = top.AddChild( new Element() );
You can now iterate over your tree: foreach( INode<Element> node in tree.All.Nodes )
DoSomething( node.Data );
or: foreach( Element element in tree.All.Values )
DoSomething( element );
DocumentationThis section details the capabilities of the collection. InstantiationThese partial class NodeTree<T>
{
public static ITree<T> NewTree() { ... }
public static ITree<T>
NewTree(IEqualityComparer<T> dataComparer) { ... }
}
The first method creates a new tree with the default The second method allows you to specify the ConversionEach interface has a property that allows conversion to the other: partial interface ITree<T>
{
INode<T> Root { get; }
}
partial interface INode<T>
{
ITree<T> Tree { get; }
}
CountsVarious metrics about a tree or node are available: partial interface ITree<T>
{
int Count { get; }
int DirectChildCount { get; }
}
partial interface INode<T>
{
int Count { get; }
int DirectChildCount { get; }
int Depth { get; }
int BranchIndex { get; }
int BranchCount { get; }
}
RelationshipsThese properties provide access to other nodes in a tree: partial interface ITree<T>
{
}
partial interface INode<T>
{
INode<T> Parent { get; }
INode<T> Previous { get; }
INode<T> Next { get; }
INode<T> Child { get; }
INode<T> Root { get; }
INode<T> Top { get; }
INode<T> First { get; }
INode<T> Last { get; }
INode<T> LastChild { get; }
}
The Boolean propertiesThese properties provide information about the relations of a node: partial interface ITree<T>
{
}
partial interface INode<T>
{
bool IsTree { get; }
bool IsRoot { get; }
bool IsTop { get; }
bool IsFirst { get; }
bool IsLast { get; }
bool HasParent { get; }
bool HasPrevious { get; }
bool HasNext { get; }
bool HasChild { get; }
}
The Adding an elementThese methods allow you to populate your tree: partial interface ITree<T>
{
INode<T> InsertChild( T o );
INode<T> AddChild( T o );
}
partial interface INode<T>
{
INode<T> InsertPrevious( T o );
INode<T> InsertNext( T o );
INode<T> InsertChild( T o );
INode<T> Add( T o );
INode<T> AddChild( T o );
}
These methods wrap the given element in a new node and insert or add this node in the tree. The Adding a treeThese methods work with complete trees: partial interface ITree<T>
{
void InsertChild( ITree<T> tree );
void AddChild( ITree<T> tree );
}
partial interface INode<T>
{
void InsertPrevious( ITree<T> tree );
void InsertNext( ITree<T> tree );
void InsertChild( ITree<T> tree );
void Add( ITree<T> tree );
void AddChild( ITree<T> tree );
}
These methods work in the same way as adding an element, but operate on complete trees. Moving a nodeThese methods allow you to move nodes around in your tree: partial interface ITree<T>
{
}
partial interface INode<T>
{
bool CanMoveToParent { get; }
bool CanMoveToPrevious { get; }
bool CanMoveToNext { get; }
bool CanMoveToChild { get; }
bool CanMoveToFirst { get; }
bool CanMoveToLast { get; }
void MoveToParent();
void MoveToPrevious();
void MoveToNext();
void MoveToChild();
void MoveToFirst();
void MoveToLast();
}
The CopyingThere are two ways to copy the sub-tree defined by a public interface IDeepCopy
{
object CreateDeepCopy();
}
If the data object supports this interface, then Manipulating sub-treesThese methods allow you to create a new tree from a node and its children: partial interface ITree<T>
{
ITree<T> Copy();
ITree<T> DeepCopy();
}
partial interface INode<T>
{
ITree<T> Cut();
ITree<T> Copy();
ITree<T> DeepCopy();
void Remove();
}
These methods operate on a node to produce a tree. Working with elementsThese methods find a node that contains the specified element and then operate on that node: partial interface ITree<T>
{
INode<T> this[ T item ] { get; }
bool Contains( INode<T> node );
bool Contains( T item );
ITree<T> Cut( T item );
ITree<T> Copy( T item );
ITree<T> DeepCopy( T item );
bool Remove( T item );
}
partial interface INode<T>
{
INode<T> this[ T item ] { get; }
bool Contains( INode<T> node );
bool Contains( T item );
ITree<T> Cut( T item );
ITree<T> Copy( T item );
ITree<T> DeepCopy( T item );
bool Remove( T item );
}
The EnumeratorsThese interfaces and members allow you to perform enumerations: public interface IEnumerableCollection<T> : IEnumerable<T>,
ICollection
{
bool Contains( T item );
}
public interface IEnumerableCollectionPair<T>
{
IEnumerableCollection<INode<T>> Nodes { get; }
IEnumerableCollection<T> Values { get; }
}
partial interface ITree<T> : IEnumerableCollectionPair<T>
{
IEnumerableCollectionPair<T> All { get; }
IEnumerableCollectionPair<T> AllChildren { get; }
IEnumerableCollectionPair<T> DirectChildren { get; }
IEnumerableCollectionPair<T> DirectChildrenInReverse { get; }
}
partial interface INode<T> : IEnumerableCollectionPair<T>
{
IEnumerableCollectionPair<T> All { get; }
IEnumerableCollectionPair<T> AllChildren { get; }
IEnumerableCollectionPair<T> DirectChildren { get; }
IEnumerableCollectionPair<T> DirectChildrenInReverse { get; }
}
The The four properties that return SerializationThis implementation supports serialization to binary or XML streams: partial interface ITree<T>
{
void XmlSerialize( Stream stream );
}
[ Serializable ]
partial class NodeTree<T> : ITree<T>,
INode<T>, ISerializable
{
public static ITree<T> XmlDeserialize( Stream stream )
}
Binary serialization is provided through the private void BinarySerialize()
{
using ( Stream stream = File.Open( Filename,
FileMode.Create, FileAccess.Write ) )
{
IFormatter f = new BinaryFormatter();
ITree<Element> tree = CurrentNode.Copy();
f.Serialize( stream, tree );
}
}
private ITree<Element> BinaryDeserialize()
{
using ( Stream stream = File.Open( Filename,
FileMode.Open, FileAccess.Read ) )
{
IFormatter f = new BinaryFormatter();
return ( ITree<Element> ) f.Deserialize( stream );
}
}
To use binary serialization, your element data type must be marked with the XML serialization is provided by methods exposed in the private void XMLSerialize()
{
using ( Stream stream = File.Open( Filename,
FileMode.Create, FileAccess.Write ) )
{
ITree<Element> tree = CurrentNode.Copy();
tree.XmlSerialize( stream );
}
}
private ITree<Element> XMLDeserialize()
{
using ( Stream stream = File.Open( Filename,
FileMode.Open, FileAccess.Read ) )
{
return NodeTree<Element>.XmlDeserialize( stream );
}
}
To use the XML serialization, your element data type must support standard XML serialization. By default, the XML serializer serializes all EventsThe two interfaces, partial interface ITree<T>
{
event EventHandler<NodeTreeDataEventArgs<T>> Validate;
event EventHandler Clearing;
event EventHandler Cleared;
event EventHandler<NodeTreeDataEventArgs<T>> Setting;
event EventHandler<NodeTreeDataEventArgs<T>> SetDone;
event EventHandler<NodeTreeInsertEventArgs<T>> Inserting;
event EventHandler<NodeTreeInsertEventArgs<T>> Inserted;
event EventHandler Cutting;
event EventHandler CutDone;
event EventHandler<NodeTreeNodeEventArgs<T>> Copying;
event EventHandler<NodeTreeNodeEventArgs<T>> Copied;
event EventHandler<NodeTreeNodeEventArgs<T>> DeepCopying;
event EventHandler<NodeTreeNodeEventArgs<T>> DeepCopied;
}
partial interface INode<T>
{
event EventHandler<NodeTreeDataEventArgs<T>> Validate;
event EventHandler<NodeTreeDataEventArgs<T>> Setting;
event EventHandler<NodeTreeDataEventArgs<T>> SetDone;
event EventHandler<NodeTreeInsertEventArgs<T>> Inserting;
event EventHandler<NodeTreeInsertEventArgs<T>> Inserted;
event EventHandler Cutting;
event EventHandler CutDone;
event EventHandler<NodeTreeNodeEventArgs<T>> Copying;
event EventHandler<NodeTreeNodeEventArgs<T>> Copied;
event EventHandler<NodeTreeNodeEventArgs<T>> DeepCopying;
event EventHandler<NodeTreeNodeEventArgs<T>> 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 default Points of interestSerializationNote the use of EventsI 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 Version 2: The ConclusionThis 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 History
| ||||||||||||||||||||