2

There is a class that stores the tree of elements. Child elements are stored in

public List<BaseTreeData> Child { get; set; }

I want to display this tree as a "flat" (linear) list of all elements. After the class is divided into two (base and heir), the GetChildren method generates an error about the type mismatch. Most likely everything is logical, but how to fix it?

Error CS1503 Argument 1: cannot convert from 'ConsoleApplication1.BaseTreeData' to 'ConsoleApplication1.TreeData'

namespace ConsoleApplication1
{
    class Program
    {        
        static void Main(string[] args)
        {
            var data = new List<TreeData>();
            for (int i = 0; i < 5; i++)
            {
                var item = new TreeData() { Name = i.ToString() };
                for (int j = 0; j < 3; j++)
                {
                    var number = (i + 1) * 10 + j;
                    item.Child.Add(new TreeData() { ID = number, Name = number.ToString(), Parent = item });                    
                }
                data.Add(item);
            }

            foreach (var item in data.SelectMany(x => GetChildren(x)))
            {
                Console.WriteLine(item.ID + " " + item.Name + " " + item.IsChecked);
            }
        }

        static IEnumerable<TreeData> GetChildren(TreeData d)
        {
            return new[] { d }.Concat(d.Child).SelectMany(x => GetChildren(x));
        }
    }

    class BaseTreeData
    {
        public bool IsChecked { get; set; }
        public BaseTreeData Parent { get; set; }
        public List<BaseTreeData> Child { get; set; }

        public BaseTreeData()
        {
            Child = new List<BaseTreeData>();
        }
    }

    class TreeData : BaseTreeData
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }
}
Ehsan Sajjad
  • 61,834
  • 16
  • 105
  • 160
egeo
  • 171
  • 1
  • 19
  • There's a very useful traversal method by Eric Lippert that is perfect for getting descendants of nodes like this: https://stackoverflow.com/a/20335369/1316856 – Parrish Husband Sep 03 '18 at 14:28
  • @MongZhu yes you're exactly correct. That's why the classes need to change. You want to be able to do `treenode.Parent.Parent.Children` and still have the correct derived type. – Parrish Husband Sep 03 '18 at 14:34
  • @ParrishHusband "you're exactly correct." I am not so sure anymore. – Mong Zhu Sep 03 '18 at 14:49
  • "MyNode" in that answer is my "BaseTreeData" or "TreeData"? – egeo Sep 03 '18 at 14:53
  • @egeo that example is an extension method, which may not be the right fit here. Since this is a class you control, you can create a method directly on the `BaseTreeData` class. However the type should be `T` if you've implemented generics. – Parrish Husband Sep 03 '18 at 14:57

2 Answers2

1

Error CS1503 Argument 1: cannot convert from 'ConsoleApplication1.BaseTreeData' to 'ConsoleApplication1.TreeData'

This error occurs because the child nodes are BaseTreeData instead of TreeData.

Using the BaseTreeData class definition you posted, child and parent will always return the base type.

Instead you can solve this with generics so that child nodes will be the same type as the parent class:

class BaseTreeData<T> where T : BaseTreeData<T>
{
    public bool IsChecked { get; set; }
    public T Parent { get; set; }
    public List<T> Children { get; set; }

    public BaseTreeData()
    {
        Children = new List<T>();
    }

    public IEnumerable<T> GetAncestors()
    {
        if (Parent == null)
            yield break;

        T relative = Parent;
        while (relative != null)
        {
            yield return relative;
            relative = relative.Parent;
        }
    }

    public IEnumerable<T> GetDescendants()
    {
        var nodes = new Stack<T>();
        nodes.Push(this as T);

        while (nodes.Any())
        {
            var current = nodes.Pop();
            yield return current;

            foreach (var childNode in current.Children)
                nodes.Push(childNode);
        }
    }
}

class TreeData : BaseTreeData<TreeData>
{
    public int ID { get; set; }
    public string Name { get; set; }
}
Parrish Husband
  • 3,148
  • 18
  • 40
  • If I change only the description of the classes as you said, I get an error "Process is terminated due to StackOverflowException." – egeo Sep 03 '18 at 14:43
  • @egeo see my linked comment to your question on how you solve that. – Parrish Husband Sep 03 '18 at 14:43
  • @egeo I added in examples on how you can traverse up/down in your structure – Parrish Husband Sep 03 '18 at 15:22
  • 1
    I tried your approach so "var result = data.SelectMany(x => x.GetDescendants()).ToList();" - work like a charm! – egeo Sep 03 '18 at 21:05
  • @egeo even this should work: `var result = data.GetDescendants().ToList();` – Parrish Husband Sep 03 '18 at 21:33
  • Although this approach may work, it's solving a problem that does not exist. Proper application of OOP design should give you what you want without workaround code (see my answer below). – JuanR Sep 04 '18 at 13:43
  • @JuanR I'll concede to this solution possibly being an example of YAGNI. Would you concede to your answer possibly being shortsighted then? Using generics is hardly an anti-pattern. Even with the proposed changes the class is still very lightweight. – Parrish Husband Sep 04 '18 at 18:32
  • @ParrishHusband: I am afraid I cannot do that. The answer I posted uses proper OOP design principles, at least as far as I know. The generic solution may certainly work but it's adding unnecessary overhead to something that is natively supported, if properly implemented. If still in doubt, take a look at how Windows Forms child controls work and you will find the same pattern, based on OOP concepts like inheritance. – JuanR Sep 04 '18 at 18:55
  • There only issue in the method `GetDescendats` is you collect the node itself as descendant but it is not true – ALT Oct 07 '21 at 15:57
-1

Your base class property Child is of type List<BaseTreeData> but you are attempting to call static method GetChildren which expects an object of type TreeData. You are essentially trying to upcast your object. How would the compiler know what to populate ID and Name with?

A more elegant approach would be to let each class decide what its string representation looks like. This eliminates the need for a GetChildren method, since you can just use the base class' Child property:

foreach (var item in data.SelectMany(x => x.Child))
{
    Console.WriteLine(item.ToString());
}

You then override the ToString implementations so that the base class provides its values and the derived class builds upon that:

class BaseTreeData
{
    //Other stuff here
    //...
    public override string ToString()
    {
        return IsChecked.ToString();
    }
}

class TreeData : BaseTreeData
{
    //Other stuff here
    //...
    public override string ToString()
    {
        var format = "{0} {1} {2}";
        var stringRepresentation = string.Format(format, ID, Name, base.ToString());
        return stringRepresentation;
    }
}

Notice the call to base.ToString() in the parameters.

Output:

10 10 False

11 11 False

Community
  • 1
  • 1
JuanR
  • 7,405
  • 1
  • 19
  • 30
  • `"A more elegant approach"` to what, writing to console? Fundamentally this is a tree structure, getting rid of `GetChildren` doesn't seem like an actual solution. – Parrish Husband Sep 03 '18 at 14:50
  • @ParrishHusband: It's certainly a more object oriented approach than trying to print what you "think" the object looks like. Your code should never make assumptions. – JuanR Sep 03 '18 at 14:56
  • I don't disagree with your comment regarding the types being wrong. I guess we just disagree on where the actual problem lies. Since `treeNode.Parent` and `treeNode.Children` are not returning the same derived type in OPs example, the class is broken. – Parrish Husband Sep 03 '18 at 15:01
  • @ParrishHusband: by the way, these are fundamentally objects. The OP is trying to get a string representation of them. That is what the `ToString` method is there for and that is why it is overridable, so you can provide your own version of what you think that should look like. – JuanR Sep 03 '18 at 15:02
  • `"OP is trying to get a string representation of them"`. This is where we disagree. OP is trying to create a tree node class that actually works. In today's example, it's printing the node structure, what about tomorrow's? – Parrish Husband Sep 03 '18 at 15:04
  • Go read the original post again: `I want to display this tree as a "flat" (linear) list of all elements`... – JuanR Sep 03 '18 at 15:05
  • we'll respectfully agree to disagree on what the right answer is here. I also do believe having `ToString` overridden makes sense, for the exact reasons you've stated. I believe it's very much secondary to fixing the actual classes. Otherwise OP will want to do `treeNode.Parent.Name` and wonder why he's not getting that property. Then we're right back here. – Parrish Husband Sep 03 '18 at 15:11
  • @ParrishHusband: I don't see anything wrong with the class design, aside from the lack of `ToString` method usage. The real issue is **how** he is trying to consume the classes. If, on the other hand, he wants to be able to access the properties in the manner you describe, he can always hide the base implementation of `Child` in `TreeData` with the `new` keyword and provide his own, type-aware version: `public new List Child { get; set; }` – JuanR Sep 03 '18 at 15:26
  • you end up forcing derived classes (`TreeData`) to specify this or else they lose the derived type? While that is certainly an approach one could take, do you truly feel generics has no benefit over that? – Parrish Husband Sep 03 '18 at 15:34
  • @ParrishHusband: With all due respect, I suggest you brush up your knowledge of OOP as well as how generics are implemented on the background (what the compiler really does under the covers). – JuanR Sep 03 '18 at 15:40
  • noted, I appreciate your interest in my professional development. Do you have any specific sources you'd recommend I read? – Parrish Husband Sep 03 '18 at 15:43
  • 1
    There is tons of stuff online. One can always Google it. That being said, I strongly recommend **Code Complete**. One of the best books I have ever read: https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670/ – JuanR Sep 03 '18 at 15:48
  • added that one to my cart, thanks for the suggestion. I have one that you may find interesting also: https://www.microsoft.com/en-us/research/wp-content/uploads/2001/01/designandimplementationofgenerics.pdf – Parrish Husband Sep 03 '18 at 15:56