0

I am trying to implement a tree data structure in C#, which I've done a 100 times in C++ But here when I'm trying to keep the Tree generic in order to accept any data type, I've made each Node have it's data in an object of type Object, as shown here:

    public class Node
    {
        public object Data;
        public Node Right;
        public Node Left;

        public Node(object value)
        {
            NodeContent = value;
            Right = Left = null;
        }
    }

But when I'm trying to implement the insert, I need to compare two objecs of type Object in order to know whether to insert a new node on the left or right side of the current node. When I try to check this

    if(value < childPtr.Data)

where value is the value to be inserted, I get an error saying that I can't compare two objects of type Object. So is there a way around this?

Ahmed Anwar
  • 688
  • 5
  • 25
  • 3
    If you want to make the tree generic, then you should use Generics! Make it a `class Node where T : IComparable` and use `public T Data;`. Then you can do `value.CompareTo(childPtr.Data)`. – Blorgbeard Jan 25 '16 at 19:05
  • When you say `value < childPtr.Data` you're already implying the object is a value type, so why use `object` then? – Alexander Derck Jan 25 '16 at 19:05
  • @AlexanderDerck I used object so I can keep the Data variable generic, so the tree will accept any data type. – Ahmed Anwar Jan 25 '16 at 19:06
  • http://stackoverflow.com/questions/4332635/c-sharp-compare-two-objects-of-unknown-types-including-reference-and-value-type – jcc Jan 25 '16 at 19:06
  • 1
    If I create two new objects, `var o1 = new object(); var o2 = new object();`... which of those is "bigger" than the other? What I insert a floating point number and then a string - how would you compare those? If you want to do this generically, you should use generics... at which point you can add a constraint that `T` implements `IComparable`... – Jon Skeet Jan 25 '16 at 19:06
  • @Blorgbeard that makes sense to me. Thanks! – Ahmed Anwar Jan 25 '16 at 19:07

2 Answers2

3

The problem with your approach is that Data can be any type. Your design allows you to put multiple types into the same tree. So one node might have a string and the other might have a double, and a third could have a reference to a user-defined type. If you make the Node constructor take an IComparable instance, you're assuming that all of the items in the tree will be of the same type, or that their IComparable interface implementations know how to compare values of all possible data types.

In short, whereas what you did will work, it's not at all type safe.

What you have is a very C-like thing to do. In C++, you'd use templates to avoid this kind of abomination. In C#, you use generics.

I echo the suggestion made in the comments: if you want a generic data structure, make a generic data structure. Using the built-in collections, for example, if you want a list of integers you write:

var list_of_integers = new List<int>();

And if you want a list of strings:

var list_of_strings = new List<string>();

If you want to create a generic tree collection, you'd start with:

public class MyGenericTree<T>
{
    public class Node
    {
        public T Data;
        public Node Left;
        public Node Right;

        public Node(T data)
        {
            Data = data;
        }
    }

    private readonly IComparer<T> _comparer;

    public MyGenericTree(IComparer<T> comparer = null)
    {
        _comparer = comparer ?? Comparer<T>.Default;
    }
}

And you create it with:

var myTree = new MyGenericTree<string>(); // or int, or whatever type

If you want a custom comparison function, you write:

// Create a tree that stores case-insensitive strings
var myTree = new MyGenericTree<string>(StringComparer.CurrentCultureIgnoreCase);

This forces the Data to always be of a compatible type. And you either use the default comparer for that type, or the comparer interface that you pass to the constructor.

And when doing the comparisons, it's:

int a = _comparer.Compare(node1, node2);
if (a < 0)
    // node1 < node2
else if (a > 0)
    // node1 > node2
else
    // node1 == node2

If you really do want to store untyped object references in the tree, you can easily write:

var myTree = new MyGenericTree<object>(some_object_comparer);

Although why you'd want to do such a thing is a bit of a mystery.

I know that the generics look a little weird to start, but after a day or so of working with them, you grasp the idea that they're quite flexible as well as being type safe.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • 1
    I strongly agree with Jim. Mine answer was just quick fix while Jim's leads to better approach. Generics fits perfectly well for the problem described. – serhiyb Jan 27 '16 at 02:55
  • @Jim Mischel Thank you, this actually makes more sense. I appreciate your help. – Ahmed Anwar Jan 27 '16 at 14:43
1

Since you require comparison from your data a better approach would be :

public class Node
{
    public IComparable Data;
    public Node Right;
    public Node Left;

    public Node(IComparable value)
    {
        Data = value;
        Right = Left = null;
    }
}

and later you can do:

if(value.CompareTo(childPtr.Data) < 0)
serhiyb
  • 4,753
  • 2
  • 15
  • 24
  • I'm guessing CompareTo() returns 0 when they're equal, less that 0 when value is less that Data and greater than 0 when Data is greater than value. Am I right? – Ahmed Anwar Jan 25 '16 at 19:13
  • I really want to downvote this answer, not because it's incorrect but because it's perpetuating a really terrible development practice. Unfortunately, despite it being a horrible solution to the problem of building a "generic" data structure, it solves the OP's immediate problem. I do want to stress, though, that it would have been much better to dissuade the OP from continuing down his misguided path. – Jim Mischel Jan 27 '16 at 02:47