Click here to Skip to main content
15,894,907 members
Please Sign up or sign in to vote.
4.89/5 (2 votes)
See more:
I do not use yield often but have run into something that I am confused by.

A code base has the following:

XML
public IEnumerable<dynamic> Points
{
    get {
        foreach (var o in GetData) yield return o;
    }
}

private IEnumerable<dynamic> GetData()
{
    if (Model.Plan == null ||
        Model.Plan.Points == null ||
        Model.Plan.Points.Datas== null)
    {
        yield return null;
    }

    foreach (var data in Model.Plan.Points.Datas)
    {
          yield return data;
    }
}


Please don't get hung up on the objects and their names. It really shouldn't matter.

The behavior I am seeing is the Model.Plan.Points is null, but I get a null reference in the foreach loop that is referencing the datas inside the Points. Shouldn't the previous conditional not allow that?
if(Model.Plan.Points == null) yield return null;
Posted

Because the yield return null; leaves the iterator in a state where it believes there are more items, the foreach loop in Points next goes to the foreach loop here, which then gives the null reference exception.
So this method must not proceed to the foreach if the null-check branch is taken.
Just put the foreach in the else clause!
C#
private IEnumerable<dynamic> GetData()
{
    if (Model.Plan == null ||
        Model.Plan.Points == null ||
        Model.Plan.Points.Datas== null)
    {
        yield return null;
    }
    else
    {
        foreach (var data in Model.Plan.Points.Datas)
        {
            yield return data;
        }
    }
}

That being said, this can be greatly simplified, since Model.Plan.Points.Datas is obviously an IEnumerable<dynamic>:
C#
private IEnumerable<dynamic> GetData()
{
    if (Model.Plan == null ||
        Model.Plan.Points == null ||
        Model.Plan.Points.Datas== null)
    {
        return Enumerable.Empty<dynamic>();
    }
    else
    {
        return Model.Plan.Points.Datas;  //.Cast<dynamic>() if necessary.
    }
}
 
Share this answer
 
v2
Comments
[no name] 12-Feb-14 14:05pm    
Thank you!

Makes sense. +5
Matt T Heffron 12-Feb-14 14:35pm    
You're welcome.
Please formally "accept" one (or more) of the Solutions that helped, using the green "Accept Solution" button.
While yield return o does return null here, the question is what are you doing with it afterwards? Basically, the yield return null is sending null back up to the Points call and is returned back to the level above. What you need to do in this call is wrap the yield return o up so that it doesn't yield if it gets null. Rewrite it like this:
C#
public IEnumerable<dynamic> Points
{
  get
  {
    foreach (var o in GetData())
    {
      if (o != null)
      {
        yield return o;
      }
    }
  }
}
Now you can call this confidently knowing that you will get data without null values outside.

Okay, I see the issue you are getting now. The point that's causing you a problem is a misunderstanding of what yield actually does here. Basically, while it yields a value back to the caller, it will still continue processing inside this method. It doesn't act like a return statement, it merely yields the value back to the caller. Potentially, with a yield statement, you could have multiple values coming out of one call.

So, the fact that the next statement works on Model.Plan.Points may fail because Model.Plan or Model.Plan.Points is null. You could work around this by putting that part in an else clause.

[Edit]Here's a way to really blow your mind with a yield - assume that the model is fully populated :
C#
private IEnumerable<dynamic> GetData()
{
  foreach (var data in Model.Plan.Points)
  {
    yield return data;
  }
  foreach (var data in Model.Plan.Points)
  {
    yield return data;
  }
}
 
Share this answer
 
v3
Comments
[no name] 12-Feb-14 12:25pm    
That does not seem to solve the problem. I added the conditional but I still get a null reference on the foreach.
Resharper is telling me I have a potential null reference as well. I am confused as to why. It seems it should be caught in it. I am able to get it to run safely by placing the foreach inside of the inverted if statement. But it seems that should not be necessary.
Pete O'Hanlon 12-Feb-14 12:50pm    
I updated my answer to explain what's really going on - basically yield doesn't behave in the way you are expecting it to.
[no name] 12-Feb-14 14:04pm    
OK, thank you. That makes sense now :-)
+5
Pete O'Hanlon 12-Feb-14 14:18pm    
You're welcome. I'm glad to be able to help.
I think yield return does not exit from the method.
Maybe:
C#
    if (Model.Plan == null ||
        Model.Plan.Points == null ||
        Model.Plan.Points.Datas== null)
    {
       return null;
    }
// ...
 
Share this answer
 
Comments
[no name] 12-Feb-14 12:00pm    
That will not compile as an iterator (a method with a yield) can not contain 'return'. It must yield return.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900