views:

1494

answers:

7

I am writing a simple WinForms application in which I allow the user to drag around TreeNodes in a TreeView control. One of the rules that I enforce is that the user is not allowed to drag a TreeNode into one of its own children. I wrote the following function in a recursive style to check for the parenthood of the destination node. Upon compilation, I get the error that not all code paths return a value for this function. As far as I can tell, I do have a return statement on every possible branch of this logic... but I'm obviously wrong. Can someone point out my error, please.

    private bool IsDestinationNodeAChildOfDraggingNode(TreeNode draggingNode, TreeNode destinationNode) {
        if (draggingNode.Nodes.Count == 0) 
            return false;
        else {
            if (draggingNode.Nodes.Contains(destinationNode)) 
                return true;
            else {
                foreach (TreeNode node in draggingNode.Nodes) 
                    return IsDestinationNodeAChildOfDraggingNode(node, destinationNode);
            }
        }
    }
+12  A: 

It's probably referring to the fact that if draggingNode.Nodes has no items in it then it will fall out of the else and exit the function without returning anything.

Maybe do this:

foreach (TreeNode node in draggingNode.Nodes) 
     return IsDestinationNodeAChildOfDraggingNode(node, destinationNode);

return false
Karim
Well, if draggingNode.Nodes has no items then its Count property would equal zero, in which case it returns false. Is that not correct?
Jonas Gorauskas
OK... I see your point and I will give it a try.
Jonas Gorauskas
The code checker doesn't know that... you are giving it credit for understanding algorithms. It only understands code paths (one of which was left out as pointed out by Karim).
Godeke
Might add something about why this is because one would think the count check in the beginning should suffice... which in a threaded and/or event driven environment can't be guaranteed to be true (though if this is what's actually in the compiler's mind or if it's just picky/simple-minded I have no idea?)
Oskar Duveborn
This solution doesn't actually work, as it returns a false if the destination is more than 2 levels deep ...
Jonas Gorauskas
@Jonas, incorrect, it returns false if the destination is not a node that is a sibling to a node on the left hand side of the graph
Jherico
"Well, if draggingNode.Nodes has no items then its Count property would equal zero [...]" True - IF (!) the Count property does anything like you would expect it to..... So, in this case conservative checking has its reasons.
dionadar
@Jherico - Yes, i realize that now.. Thanks! @Karim - I am accepting the answer by Jherico because he addresses the compiler error as well as the flaw in my algorithm.
Jonas Gorauskas
A: 

My expectation is that the foreach loop is not guaranteed to return a value, if all elements in the foreach fail to return anything, or if there are no nodes. After the final if/else/foreach, add a default return value.

JYelton
A: 

I'm guessing that the C# compiler assumes that the foreach loop terminates normally, as opposed to returning out across about three levels of control.

John R. Strohm
Karim's answer is much better than mine.
John R. Strohm
+1  A: 

The compiler thinks you need to add a return after the foreach loop as it could possibly be that there are no nodes in draggingNode.Nodes so the loop wouldn't actually execute.

private bool IsDestinationNodeAChildOfDraggingNode(TreeNode draggingNode, TreeNode destinationNode) {
    if (draggingNode.Nodes.Count == 0) 
        return false;
    else {
        if (draggingNode.Nodes.Contains(destinationNode)) 
            return true;
        else {
            foreach (TreeNode node in draggingNode.Nodes) 
            {
                return IsDestinationNodeAChildOfDraggingNode(node, destinationNode);
            }
            return false; // <-- here for example
        }
    }
}

Now this might not be the correct place for your algorithm, but the compiler thinks that there is a return missing here. Reworking your code along the lines Jherico suggests will make the situation clearer.

ChrisF
I am actually stablishing if draggingNode.Nodes has any nodes when I check *if (draggingNode.Nodes.Count == 0)* ... so when I get to the foreach loop I already know that I have nodes to loop through... Is that not correct?
Jonas Gorauskas
@Jonas - I see your point, but adding the return at the point indicated stops the compiler complaining.
ChrisF
Adding the *return false* statement where you propose doesn't work... It returns false when the destination node is more than 2 levels deep...
Jonas Gorauskas
A: 

What if there are no items in draggingNodes.Nodes? Then you will not return a value.

Ed Swangren
A: 

Outside of the outer Else you must return something.

For instance:

private bool IsDestinationNodeAChildOfDraggingNode(TreeNode draggingNode, TreeNode destinationNode) {
if (draggingNode.Nodes.Count == 0) 
        return false;
else {
       if (draggingNode.Nodes.Contains(destinationNode)) 
            return true;
       else {
            foreach (TreeNode node in draggingNode.Nodes) 
            return IsDestinationNodeAChildOfDraggingNode(node, destinationNode);
            }
    }
//Return something here.
}
Sergio Tapia
Incorrect: If the problem with the `foreach` loop is addressed, both branches of the inner if/else will return values and the compiler will be happy.
Bevan
+8  A: 

Actually I think your algorithm is wrong. Why would you bother with a foreach statement if you're only going to check the first value? Also, all the else's after the returns are kind of redundant and make the code harder to follow.

I suspect what you're trying to do is something more like this:

private bool IsDestinationNodeAChildOfDraggingNode(TreeNode draggingNode, TreeNode destinationNode) {
  // special case, no children
  if (draggingNode.Nodes.Count == 0) 
    return false;

  // check if the target is one of my children
  if (draggingNode.Nodes.Contains(destinationNode)) 
     return true;

  // recursively check each of my children to see if one of their descendants is the target
  foreach (TreeNode node in draggingNode.Nodes) 
    if (IsDestinationNodeAChildOfDraggingNode(node, destinationNode)) 
        return true;

  // didn't find anything
  return false;
}

Some people will insist that early return is evil which would result in this version of the function:

private bool IsDestinationNodeAChildOfDraggingNode(TreeNode draggingNode, TreeNode destinationNode) {
  bool retVal = false;

  if (draggingNode.Nodes.Count != 0) {

    // check if the target is one of my children
    if (draggingNode.Nodes.Contains(destinationNode)) {
      retVal = true;
    } else {

      // recursively check each of my children to see if one of their descendants is the target
      foreach (TreeNode node in draggingNode.Nodes) 
        if (IsDestinationNodeAChildOfDraggingNode(node, destinationNode)) {
          retVal = true;
          break;
        }
    }
  }

  return retVal;
}
Jherico
My logic is as follows:1. If draggingNode has no nodes then destinationNode cannot be one of its children.2. Otherwise check if the immediate children of draggingNode contains the destinationNode.3. If not, then start descending into the draggingNode's grandchildren, until you run out of possibilities.BTW, your alternative where you create *bool retVal = false;* works and created the desired effect. I thought that the *early returns* version was cleaner. Why do you say that *early returns are evil*?
Jonas Gorauskas
Step 1 and 2 are fine, but step 3 is broken, because the code will always return true or false based on the children of the first child node, not *all* the grandchildren. If your second child's first child is the destination node, your code will still return false. Also, *I* didn't say early returns are evil. I said some people might say that. I've been places where the coding guidelines prohibited them. I don't know the justification off the top of my head.
Jherico
re: early return, its the subject of this question: http://stackoverflow.com/questions/36707
Jherico
Whenever an early return equals simpler, easier to follow code, I'll gladly have spaghetti for dinner.
Snarfblam
@Jherico - Thanks for the pointer to the question on Early Return... Interesting commentary going on there. I am accepting this answer by Jherico because he addresses the compiler error as well as the flaw in my algorithm.
Jonas Gorauskas