Updating an Item Property Within Ienumerable But the Property Doesn't Stay Set

Updating an item property within IEnumerable but the property doesn't stay set?

IEnumerable<T>s do not guarantee that updated values will persist across enumerations. For instance, a List will return the same set of objects on every iteration, so if you update a property, it will be saved across iterations. However, many other implementations of IEnumerables return a new set of objects each time, so any changes made will not persist.

If you need to store and update the results, pull the IEnumerable<T> down to a List<T> using .ToList() or project it into a new IEnumerable<T> using .Select() with the changes applied.

To specifically apply that to your code, it would look like this:

var transactions = (from t in db.Transactions
where t.SellingPrice != 0
select t).ToList();

var taAgents = (from ta in db.TransactionAgents
select ta).ToList();

foreach (var transaction in transactions)
{
foreach(var agent in taAgents)
{
agent.AgentCommission = ((transaction.CommissionPercent / 100) * (agent.CommissionPercent / 100) * transaction.SellingPrice) - agent.BrokerageSplit;
}
}

dataGridView1.DataSource = taAgents;

Why items of an IEnumerable variable cannot be updated when it is pointing to an IEnumerable but works when pointing to an implemented collection

As always, you need to remember that LINQ operations return queries, not the results of executing those queries.

When you call select what is returned is a query that knows how to project each item from the source collection into a new item each time an item is asked for. Each time you iterate the IEnumerable you're executing the query a new time, creating a new result set (made up of all new items) each time. You can, and in fact are updating the items each time you call ElementAt, which is why the compiler isn't stopping you. You're simply doing nothing with that item besides setting that one value and then dropping it on the floor, never to be used again. When you execute the line:

people.ElementAt(i).Name = "New Name";

You're iterating through numbers, creating a new Person for each number, until you get to the i-th value then it's returned from ElementAt, at which point the name is set, and then that value is no longer accessible from your code because you haven't saved that returened Person anywhere. When you call ElementAt(0) you're creating a person from 0, setting the name, then doing nothing. When you call ElementAt(1) you're creating two people, returning the second, setting its name, then forgetting about it. Then you call ElementAt(2), creating 3 more people, returning the 3rd, setting its name, and then forgetting about it. Then you call ToList, creating a list of all 3 people, and printing out the values of those 3 brand new people.

When you materialize the query into a collection, you can then access the items in that collection multiple times, because you have the results of a query that you're accessing multiple times, and keeping track of those same returned results, rather than a query itself that you're executing over and over again.

C# failing to set property inside IEnumerable

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ı.

Why can't I replace each object's property in a list within loop?

You need to convert groupedClassOptions to list using ToList() method which makes your object inside list to be updatable.

var groupedClassOptions = dataClass.GroupBy(x => x.grade).Select(x => new OptionGroupVM()
{
GroupName = "Grade " + (x.Key == 0 ? grade0_name : x.Key.ToString()) /*+ " (" + (groupTotal.Where(t => t.grade.ToString().Equals(x.Key)).First().total) + " students)"*/,
Options = x.Select(y => new OptionVM()
{
Value = y.classID.ToString(),
Text = x.Key == 0 ? grade0_name + y.classname.Substring(1) + " (" + y.total + " students)" : y.classname + " (" + y.total + " students)"
})
}).ToList();

Now, update object inside above list.

The reason behind not updating object inside IEnumerable is posted in following answer:

IEnumerables do not guarantee that updated values will persist
across enumerations. For instance, a List will return the same set of
objects on every iteration, so if you update a property, it will be
saved across iterations. However, many other implementations of
IEnumerables return a new set of objects each time, so any changes
made will not persist.

IEnumerableT does not reflect changes on elements

The reason is that LINQ evaluates the Select on the iteration of the IEnumerable and on EACH iteration of the IEnumerable.

This means that if you perform changes on the items returned from the Select those object will be thrown away, and on the next iteration, you will bet a new object with the original data.

Putting ToArray will cause the result of Select to be stored in a collection, so changes you perform on the objects will work as expected.

To demonstrate this behavior you can see the output if you add some Console.WriteLine:

class Item
{
public int Value { get; set; }
static int id = 0;
public int Id { get; } = id++; // generate next id every time
}
static void Main(string[] args)
{
var range = new int[] { 1, 2, 3 }
.Select(i => new Item { Value = i });

foreach (var item in range)
{
Console.WriteLine(item.Value + "- id:" + item.Id);
// Will output
//1- id:0
//2- id:1
//3- id:2
}
foreach (var item in range)
{
Console.WriteLine(item.Value + "- id:" + item.Id);
// Will output
//1- id:3
//2- id:4
//3- id:5
}
}

Item from IEnumerable changed inside foreach, but change not saved to collection

This is a matter of LINQ's lazy evaluation. Each time you iterate over list in GetBool1(), you're creating a new sequence of values. Changing the Value property in one of those objects doesn't change anything else.

Compare that with GetBool2(), where you're got a call to ToList(), which means you're creating a List<T> with a sequence of objects in. Now each time you iterate over that list, you'll get references to the same objects... so if you modify the value within one of those objects, you'll see that next time.

If you change your Select argument to

x => { Console.WriteLine("Selecting!"); return new BoolContainer { Value = x } };

... then add appropriate extra Console.WriteLine calls so you can see when it's taking place, you'll see that the delegate is never called in GetBool2() after the call to ToList(), but in GetBool1() it will be called each time you iterate over the sequence.

Why can't the properties of elements within an IEnumerable be updated?

MyClasses is an IEnumerable. This will be executed every time you access it. This in itself does not make it impossible to update one of the classes. But... You create NEW instances of MyClass every time. These obviously will have uninitialized properties. If you add .ToList() you will get the behavior you expect.

Updating items in an IEnumerable

What's happening here is that you are creating an enumerable list which you are enumerating multiple times.

Each time you enumerate list, the enumeration processes the elements of the strings list calling new Foo { A = x } for each element to create the elements of the resulting sequence.

That means the the Foo objects created by the first foreach enumeration are NOT the same as the ones created by the second foreach enumeration. New ones are created for each enumeration.

This is the reason that Resharper warns about "possible multiple enumeration".

To avoid this, you would use var list = strings.Select(x => new Foo { A = x }).ToList(); to enumerate the sequence just once and store the results in an actual List<T>.

Can't modify List inside an IEnumerable?

Yes you can't! The IEnumerable generates new Lists every time it is evaluated; it says so right there in your code:

.Select(x => new List<int>{x});

When you call Dump(), it is evaluated. When you call First(), it's evaluated again. Then Dump again. All new Lists!

If you evaluate the IEnumerable before putting it in the list variable (e.g. by putting it into an array), it would work:

var list = new int[]{1,2,3}.Select(x => new List<int>{x}).ToArray();
list.Dump();
list.First().Insert(0, 5);
list.Dump();

Update all objects in a collection using LINQ

While you can use a ForEach extension method, if you want to use just the framework you can do

collection.Select(c => {c.PropertyToSet = value; return c;}).ToList();

The ToList is needed in order to evaluate the select immediately due to lazy evaluation.



Related Topics



Leave a reply



Submit