3

So, i ran into a strange problem today.

Let's say i have a TreeNode defined like this:

TreeNode node = new TreeNode();
node.Nodes.Add(new TreeNode { Name = "aaa" });
node.Nodes.Add(new TreeNode { Name = "bbb" });

And then I call a recursive method

ColorNode(node.Nodes, Color.Green);

The method looks like this:

void ColorNode(TreeNodeCollection nodes, System.Drawing.Color Color)
{
    foreach (var child in nodes)
    {
        child.ForeColor = Color;
        if (child.Nodes != null && child.Nodes.Count > 0)
            ColorNode(child.Nodes, Color);
    }
}

In that foreach loop if I keep var child Visual Studio cries that

Object does not contain a definition for ForeColor and no extension method ForeColor accepting a first argument of type ojbect could be found.

object does not contain a definition for Nodes and no extension method Nodes accepting a first argument of type object could be found.

But if i change var child with TreeNode child everything works as expected.

Can someone explain this behaviour?

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
Zippy
  • 1,804
  • 5
  • 27
  • 36
  • Some duplicate threads: (1) [Why is an object not stongly typed in a foreach with var?](http://stackoverflow.com/questions/19133692/) (2) [Why can't I do foreach (var Item in DataTable.Rows)?](http://stackoverflow.com/questions/2325777/) (3) [Why does var evaluate to System.Object in “foreach (var row in table.Rows)”?](http://stackoverflow.com/questions/2786727/) (4) [Use of C# var for implicit typing of System.Data.Datarow](http://stackoverflow.com/questions/12622307/) – Jeppe Stig Nielsen Feb 02 '16 at 14:50

3 Answers3

7

Because TreeNodeCollection has only got an enumerator on object (it implements IEnumerable, not IEnumerable<TreeNode>), the var becomes object. Type the variable yourself and your code will compile:

foreach (TreeNode child in nodes)
{ }
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • happens to me with `DataTable.Rows` all the time, mildly annoying – Jonesopolis Feb 02 '16 at 14:42
  • An alternative, if you prefer `var` always in `foreach`, and if you want to make the cast explicit, is: `foreach (var child in nodes.Cast()) { }`. But of course your solution is less typing, and more in the spirit of .NET 1 and old non-generic C#. – Jeppe Stig Nielsen Feb 02 '16 at 14:43
  • Thanks for the answer but krillgar explains exactly why it's not working. – Zippy Feb 02 '16 at 16:17
3

As is stated in the other answers, TreeNodeCollection is a collection of object objects. This is because WinForms was a part of the C# language from the beginning (.NET 1.1), and generics weren't added until .NET 2.0. For backwards compatibility reasons, they did not alter the class to also implement IEnumerable<TreeNode>.

As you discovered, the only way to process them is by explicitly typing the objects as TreeNode within your foreach loop. That is because var wasn't added until .NET 3.5.

One other alternative (though more verbose) would be to get the objects of the type that you want:

foreach (var child in nodes.OfType<TreeNode>())

As I said, this is more verbose, and you would be better suited to just explicitly cast the objects. However, the OfType method is a good one to have in your repertoire.

Clarification

As mentioned in comments on Patrick's answer, another option is .Cast<T>(). However, if you do that, exceptions are thrown if an item cannot be cast to the specified type. In a situation like this, you are fine because based on the way that the collection is created, you won't have anything that is not a TreeNode. But, in other collections, it is possible to have other types based on super classes, etc. OfType<T>() will ignore anything that is not of the type you are requesting.

krillgar
  • 12,596
  • 6
  • 50
  • 86
  • The same happens in `WPF` with `TreeView` items. This applies to `ItemsControl` too? – Zippy Feb 02 '16 at 16:00
  • 1
    It appears so. Following down the MSDN stack from `ItemsControl` through the `Items` property, you find out it is an [ItemsCollection](https://msdn.microsoft.com/en-us/library/system.windows.controls.itemcollection(v=vs.110).aspx) which for some reason does not use generics either. Again, that is the reason why `var` doesn't work, but my reasonings for their avoidance of generics no longer hold up with WPF because they were available at that point. Perhaps to stay in line with the way WinForms was done, but that is not a design decision I would agree with. – krillgar Feb 02 '16 at 16:15
1

If you look on MSDN you will see this:

public class TreeNodeCollection : IList, ICollection, IEnumerable

notice that it is not using the generic collections so you must use the variable type and not var as object will be used instead.

TreeNodeCollection Class

Ric
  • 12,855
  • 3
  • 30
  • 36
  • Totally true, but i'm wondering why. krillgar's answer is clarifying this. – Zippy Feb 02 '16 at 16:07
  • When enumerating using `foreach`, the enumerator returns only `object`, so the type of `var` becomes `object`, not `TreeNode`. As mentioned, this is due to the version of the framework that did not have generics. – Ric Feb 02 '16 at 16:09