Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# failing to set property inside IEnumerable

Tags:

c#

.net

linq

Seen a weird bit of behaviour in some C# code that I'm at a loss to explain. Could be I'm missing an important bit of understanding, so hoping someone out there can switch on the light for me.

Got a block of code that looks like this:

    IEnumberable<myObject> objects = GetObjectsFromApiCall();

    for (int i = 0; i < objects.Count(); i++)
        {
            if (String.IsNullOrEmpty(objects.ElementAt(i).SubObject.Title))
            {
                SubObject sub = GetSubObjectFromDatabase((long)objects.ElementAt(i).SubObject.Id);
                if (sub != null)
                {
                    objects.ElementAt(i).SubObject.Title = sub.Title;
                }
            }
        }

When you step through it, everything about this code seems to work properly. The "objects" collection is populated as expected. "sub" is fetched as collected and has a full set of expected properties, including a populated Title property. No errors are thrown during execution.

But ... the SubObject.Title property (which just has standard get; set; code) that exists in each Object stubbornly remains empty.

I'm at a loss. Anyone explain what's going on?

EDIT: For those who suggested I shouldn't use a for loop and ElementAt, I started with a foreach loop but thought it might be the source of the problem because it was fetching a new SubObject each time round. Fixed now, thanks to your help, and the ForEach restored.

Cheers, Matt

like image 577
Bob Tway Avatar asked Mar 08 '13 12:03

Bob Tway


5 Answers

I would fix it this way:

var objects = GetObjectsFromApiCall().ToList();

Then you could keep the loop as is (it works), or optimize it a bit using foreach and some Linq as suggested by other answers, but it does not really matter: the problem was that you attempted to change an element on an IEnumerator<> as explained in this question pointed by @Ahmet Kakıcı.

like image 74
Larry Avatar answered Nov 11 '22 10:11

Larry


Try this

List<myObject> objects = GetObjectsFromApiCall().ToList();

foreach(var obj in objects.Where(o => string.IsNullOrEmpty(objects.SubObject.Title)).ToList())
{
    var subObject = GetSubObjectFromDatabase(obj.SubObject.Id);
    if(subObject == null) continue;

    obj.SubObject.Title = subObject.Title;
}
like image 41
Dustin Kingen Avatar answered Nov 11 '22 11:11

Dustin Kingen


First of all, you should not use ElementAt() for this kind of code, use

foreach (var o in objects)
{
    if (string.IsNullOrEmpty(o.SubObject.Title))
    {
        o.SubObject.Title = ...;
    }
}

Also you should note that if your method returns a dynamic IEnumerable then every time you call objects.Something() the API is called again and a fresh copy is retrieved. If this is the case, you should copy the enumerable into a list using .ToList() method.

There is also a way of not putting a copy in the list - by creating a dynamic enumerator like this:

objects = objects.Select(o =>
{
    if (string.IsNullOrEmpty(o.SubObject.Title))
    {
        o.SubObject.Title = ...;
    }
    return o;
});

As for the value not being set correctly (if previous things did not help) - try adding a throw new Exception(value) in the setter for Title property - see if that is being called with the correct value.

like image 40
Knaģis Avatar answered Nov 11 '22 11:11

Knaģis


I guest the function GetObjectsFromApiCall looks like following:

public IEnumberable<myObject> GetObjectsFromApiCall(){
    for(var i = 0; i < 10; i++)
    {
         yield return new myObject();
    }
}

If I'm right, every time you call objects.ElementAt(i) function to get the object, you will get a new object by "yield return new myObject()".

like image 26
fengyj Avatar answered Nov 11 '22 10:11

fengyj


But how do you check if the Title property is changed? Do you call GetObjectsFromApiCall() again? Or do you foreach through the same objects instance again?

An IEnumerable instance may create and yield new objects each time it is "enumerated". So here's a simple example for illustration. For the example, define:

class SomeObject
{
    public string Title { get; set; }
}

Then we will consider two types of "source", first an array, and then an iterator block defined like this:

  static IEnumerable<SomeObject> GetSomeSequence()
  {
      yield return new SomeObject { Title = "Alpha", };
      yield return new SomeObject { Title = "Beta", };
      yield return new SomeObject { Title = "Gamma", };
  }

Then test it this way:

  static void Main()
  {
      IEnumerable<SomeObject> thingsToModify;

      // set source to an array
      thingsToModify = new[] { new SomeObject { Title = "Alpha", }, new SomeObject { Title = "Beta", }, new SomeObject { Title = "Gamma", }, };

      foreach (var t in thingsToModify)
          Console.WriteLine(t.Title);

      foreach (var t in thingsToModify)
          t.Title = "Changed!";

      foreach (var t in thingsToModify)
          Console.WriteLine(t.Title);    // OK, modified


      // set source to something which yields new object each time a new GetEnumerator() call is made
      thingsToModify = GetSomeSequence();

      foreach (var t in thingsToModify)
          Console.WriteLine(t.Title);

      foreach (var t in thingsToModify)
          t.Title = "Changed!";          // no-one keeps these modified objects

      foreach (var t in thingsToModify)
          Console.WriteLine(t.Title);    // new objects, titles not modified

  }

Conclusion: It's perfectly possible to modify the state of a mutable object which belongs to the source we're iterating over. But some types of IEnumerable sources yield new copies of the data each time they are called, and then it's useless to make modifications to the copy.

like image 1
Jeppe Stig Nielsen Avatar answered Nov 11 '22 09:11

Jeppe Stig Nielsen