This question already has answers here:
Item from IEnumerable changed inside foreach, but change not saved to collection
(1 answer)
C# failing to set property inside IEnumerable
(5 answers)
Closed 3 years ago.
I have this code:
class Foo
{
public string A { get; set; }
}
class Program
{
static void Main(string[] args)
{
var strings = new List<string> { "foo", "foo" };
var list = strings.Select(x => new Foo { A = x });
foreach (var item in list)
{
item.A = "bar";
}
foreach (var item in list)
{
Console.WriteLine(item.A);
}
}
}
Which prints:
foo
foo
What exactly happens when you set item.A = "bar" ?
After the first foreach loop finishes, does the list var really contain the same two Foo objects with "bar" as the new string?
If so, how could you access these new values?
I understand that when the 2nd foreach loop runs, it is enumerating the collection of strings which is why you get two print outs of "foo", but I'm just confused as to what happens when item.A = "bar" is run, and if you are never able to access that new value, why does the compiler allow you to modify it?
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>.
The problem is that you haven't called ToList method to materialize your LINQ query. When you call ToList as below:
var list = strings.Select(x => new Foo { A = x })
.ToList();
an in-memory collection of Foo objects would be created, whose property value A would have the value x. Essentially two new objects of type Foo would be created with the value of A to be "foo". Then you can loop through this list and modify the property value.
Please look at this fiddle
You are right that if will not going to be chnaged, then why compiler allow. but if you want to print without updating actual item in this scenario above code will helpful.
One thing you should know that, you can not modified the item of IEnumerable object.
you have to use List();
var strings = new List<string> { "foo", "foo" };
var list = strings.Select(x => new Foo { A = x }).ToList();
foreach (var item in list)
{
item.A = "bar";
}
foreach (var item in list)
{
Console.WriteLine(item.A);
}
Related
This question already has answers here:
No generic implementation of OrderedDictionary?
(13 answers)
Closed 1 year ago.
Ok, I give up. It's probably something really simple I'm missing but I've been stuck on this for the last 2 hours and I can't find the answer anywhere online. The below code shows a CS1579 error in my list inside my foreach saying that:
"foreach statement cannot operate on variables of type 'object' because 'object' does not contain a public definition for 'GetEnumerator'".
Shouldn't my roomList[roomNameTest] return a List<string>?
var roomList = new OrderedDictionary();
var listA = new List<string>();
listA.Add("elemA");
listA.Add("elemB");
roomList["roomA"] = listA;
var roomNameTest = "roomA";
if (roomList.Contains(roomNameTest))
{
var list = roomList[roomNameTest];
foreach (var item in list)
{
Console.WriteLine(item);
}
}
SOLUTION:
Using a Dictionary<string, List<string>>() worked really well and I used the OrderBy(x => x.Key) to sort the dictionary later on as I needed. Thanks everybody for the contribution, much appreciated.
OrderedDictionary isn't a generic class so it works with objects only. You will need to cast or use a different type of collection. For example:
var list = (List<string>) roomList[roomNameTest];
But that might lead to issues if you add something that is not a <List<string> to the dictionary.
Also, I'm not really sure that you need to use OrderedDictionary here, and a strongly typed Dictionary<string, List<string>> would be much better. For example:
var roomList = new Dictionary<string, List<string>>();
//...
if (roomList.ContainsKey(roomNameTest))
{
//...
}
Though I would also recommend using TryGetValue:
if(roomList.TryGetValue(roomNameTest, out var list))
{
foreach (var item in list)
{
Console.WriteLine(item);
}
}
Well, since items of OrderedDictionary declared being of type object, .Net treats them as
instances of object, which don't imeplement IEnumerable. For instance you can easily put
roomList.Add("abc", 123);
roomList.Add("def", true);
roomList.Add("pqr", "bla-bla-bla");
You can try casting these items
into IEnumerable<string> and on success looping:
var roomList = new System.Collections.Specialized.OrderedDictionary();
var listA = new List<string>();
listA.Add("elemA");
listA.Add("elemB");
// Note "Add", since item with Key == "roomA" doesn't exist
roomList.Add("roomA", listA);
var roomNameTest = "roomA";
if (roomList.Contains(roomNameTest)) {
// if item implements IEnumerable<string>, say it List<string>
// we can loop over it
if (roomList[roomNameTest] is IEnumerable<string> list)
foreach (var item in list) {
Console.WriteLine(item);
}
}
I'm having a strange behavior when adding an object to an ObservableCollection and then looking for it. Just after adding it is found and then with same code it isn't anymore?
public class TestClass {
public TestClass(string s) {
Str = s;
}
public string Str {
get;
set;
}
}
private ObservableCollection<TestClass> testCollection = new ObservableCollection<TestClass>();
private List<string> newValueList = new List<string> { "one", "two", "three" };
private void Test() {
var tmpList = newValueList.Select(p => new TestClass(p));
foreach (var v in tmpList) {
testCollection.Add(v);
if (testCollection.Contains(v))
Console.WriteLine("YES");
else
Console.WriteLine("NO");
}
foreach (var v in tmpList) {
if (testCollection.Contains(v))
Console.WriteLine("IN");
else
Console.WriteLine("OUT");
}
}
Running this code will result in the output: YES YES YES OUT OUT OUT
When using .ToList() to tmpList you will get the expected result.
You defined a class that runtime does not know how to compare them. So it assumes that two of them are equal when they have same reference not same Str. In another word two object of your TestClass (a,b) are equal when a and b are same addresses. If you want to change this and make a and b equal when they have same Str you should override Equals and GetHashCode. You can read more here or here or here
Part one:
foreach (var v in tmpList) {
testCollection.Add(v);
if (testCollection.Contains(v))
Console.WriteLine("YES");
else
Console.WriteLine("NO");
}
here you are adding v to collection and check if v is inside it so it will return "YES".
Part Two
foreach (var v in tmpList) {
if (testCollection.Contains(v))
Console.WriteLine("IN");
else
Console.WriteLine("OUT");
}
here you are looking for v (which is not the exact same reference to objects in your collection (because lazy evaluation generates new instance in foreach iterations every time)) in your collection so it will return "OUT"!
Select method returns an IEnumerable object which calls GetEnumerator whenever its used in loop hence calling the Lambda in select method on every element in the list seprately for both loops.
Loop1: Select(p => new TestClass(p))
Loop2: Select(p => new TestClass(p))
so whenever the loop uses the tmpList
for both loops select statement is executed which calls lambda
hence creating Distinct set of objects for every loop.
You can verify this behaviour by creating a breakpoint in the lambda expression.
You will see that its called 6 times not 3.
The problem is that tmpList is not a list, but a "lazy" iterator which will create new objects each time you foreach it.
Correct the line:
var tmpList = newValueList.Select(p => new TestClass(p)).ToList();
This question already has answers here:
Update all objects in a collection using LINQ
(18 answers)
Closed 7 years ago.
I am trying to add a SELECT ALL functionality to my grid with LINQ, but I get a compilation error.
List<Person_> PeopleUpdated = People.ToList().ForEach(a => a.SELECTED = true).ToList();
It says
Cannot implicitly convert type 'void' to
'System.Collections.Generic.List <
LogoSapHrIntegration.frmXmlUpload.Person_>'
what am I doing wrong?
The List<T>.ForEach has no return value (ie void), so you can't run ToList() against that. (see MSDN)
ForEach a specific action for each item in the list (just like doing a real for loop).
In your case a simple for loop to select all is most efficient.
foreach (var person in People)
person.Selected = true
List<T>.ForEach returns void (in your case, it changes your collection in place). ForEach takes an Action<T> and executes that on each item of your list.
See List(T).ForEach on MSDN
The ForEeach method (which is not LINQ) runs an action on each item in the list, it's not used to filter out items from a list so it doesn't return a result.
Just run the method on each item; there is no result to assign:
People.ToList().ForEach(a => a.SELECTED = true);
If you wanted a new list of items where the property was changed, you would need to clone the items to make them separate from the originals:
List<Person_> PeopleUpdated = People.ToList().Select(a => {
Person_ b = a.Clone();
b.SELECTED = true;
return b;
}).ToList();
(If the class doesn't support cloning, you would need to implement the Clone method (and preferably the IClonable interface).)
First of all, you can use a normal foreach loop:
foreach (var person in people)
{
person.Selected = true;
}
Which is the simplest and cleanest.
If you really want to jump to hoops and use LINQ, you can use ConvertAll:
var list = new List<Person> { new Person(), new Person() };
var convertedPeople = list.ConvertAll(person =>
{
person.Selected = true;
return person;
});
[CrossPost From MSDN]
I had a task that, I need to send a generic List to a method, where I need to iterate it and convert it to an Excel File. I already did this with Data Table, but with Generic list I am facing some problems (I don't want to convert my generic list to Data Table). I will paste the code which helps me out for an answer.
I Had Two Generic Lists
List<User> objList = new List<User>();
List<Student> objStudent = new List<Student>();
// I am adding some Item to List
User obj = new User(1, "aaa");
User obj1 = new User(2, "bbb");
User obj2 = new User(3, "ccc");
User obj3 = new User(4, "ddd");
Student sobj = new Student(1, "aaa");
Student sobj1 = new Student(2, "bbb");
Student sobj2 = new Student(3, "ccc");
Student sobj3 = new Student(4, "ddd");
objList.Add(obj);ExportToExcel(objList);
To Export it to Excel , I am passing the lists to the below methods as
public void ExportToExcel<T>(IEnumerable<T> list)
{
PropertyInfo[] piT = typeof(T).GetProperties();
var Users = list.ToList();
Type myType = (typeof(T));
}
When I am passing my list to Ienumerable... I am not able to retrieve the data present in the List IEnumerable list. If I retrieve the data , then I can handle further. Could any one suggest me the better Idea?
If your always going to work with List<T> you could change IEnumerable<T> to IList<T>. AFAIK the IEnumerable interface does not define methods for accessing the data inside the collection, only to iterate it.
You could even use ICollection<T> if it suits your needs.
If you need to access the values of all the properties on type T, you can use the PropertyInfo.GetValue method:
public void ExportToExcel<T>(IEnumerable<T> items)
{
var properties = typeof(T).GetProperties();
foreach(var item in items)
{
foreach(var property in properties)
{
var value = property.GetValue(item, null);
// Do something else with the property's value
}
}
}
Edit in response to comment
You indicated you might receive a single list or a list of lists. You can add another overload which takes the composed lists, then iterate through it and export each individual list:
public void ExportToExcel<T>(IEnumerable<IEnumerable<T>> itemSets)
{
foreach(var itemSet in itemSets)
{
ExportToExcel(itemSet);
}
}
In C#, I have noticed that if I am running a foreach loop on a LINQ generated IEnumerable<T> collection and try to modify the contents of each T element, my modifications are not persistent.
On the other hand, if I apply the ToArray() or ToList() method when creating my collection, modification of the individual elements in the foreach loop are persistent.
I suspect that this is in some way related to deferred execution, but exactly how is not entirely obvious to me. I would really appreciate an explanation to this difference in behavior.
Here is some example code - I have a class MyClass with a constructor and auto-implemented property:
public class MyClass
{
public MyClass(int val) { Str = val.ToString(); }
public string Str { get; set; }
}
In my example application I use LINQ Select() to create two collections of MyClass objects based on a collection of integers, one IEnumerable<MyClass>, and one IList<MyClass> by applying the ToList() method in the end.
var ints = Enumerable.Range(1, 10);
var myClassEnumerable = ints.Select(i => new MyClass(i));
var myClassArray = ints.Select(i => new MyClass(i)).ToList();
Next, I run a foreach loop over each of the collections, and modify the contents of the looped-over MyClass objects:
foreach (var obj in myClassEnumerable) obj.Str = "Something";
foreach (var obj in myClassArray) obj.Str = "Something else";
Finally, I output the Str member of the first element in each collection:
Console.WriteLine(myClassEnumerable.First().Str);
Console.WriteLine(myClassArray.First().Str);
Somewhat counter-intuitively, the output is:
1
Something else
Deferred execution is the indeed the key point.
Executing myClassEnumerable.First().Str will reexecute your query ints.Select(i => new MyClass(i)); and so it will give you a new IEnumerable with a new list of integers.
You can see this in action using your debugger. Put a breakpoint at the new MyClass(i) part of the IEnumerable select and you will see that this part get's hit again when you execute it for Console.WriteLine
You are right, it is deferred execution. A new MyClass instance is created each time you iterate the IEnumerable. By calling ToList or ToArray you then create a List or Array and populate it with the new MyClass instances created from the iteration of the IEnumerable.