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 IEnumerable
s 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
How to Disable a System Device Programmatically
Force Download of a File on Web Server - Asp .Net C#
Difference Between "Var" and "Object" in C#
What Is the Use for Task.Fromresult<Tresult> in C#
How Would You Make a Unique Filename by Adding a Number
How to Convert Ienumerable to Observablecollection
Could Not Load File or Assembly 'Microsoft.Reportviewer.Common, Version=11.0.0.0
Why C# Is Not Allowing Non-Member Functions Like C++
How to Get the Actual Monitor Name? as Seen in the Resolution Dialog
I Didn't Find "Zipfile" Class in the "System.Io.Compression" Namespace
.Net 4.0 and the Dreaded Onuserpreferencechanged Hang
Should I Store My Images in the Database or Folders
Creating a Temporary Directory in Windows
How to Delete a File Which Is Locked by Another Process in C#