C#: Update Item value in List<of T> - c#

I'm looking for a way to update an Element in a List without enumerating it on my own.
I got the Class MyProjects which hold a List named Projects.
I want to find the MyProjects.Projects-Class, where a member property of Class1 (Name) equals the Value "Overhead".
What works:
foreach (Project prj in MyProjects.Projects) {
if (prj.Name == "Overhead")
prj.IsActive = true;
};
I, however, try to do the same by using Linq, but failed in writing it as one line. Is this even possible? The reason why I don't like to iterate in the way above is that I already iterate the whole list in this codeblock and think, that there might be a more beautiful way :)

You shouldn't try to get everything down to one line - just as brief as is readable. In this case, you can use:
foreach (var project in MyProjects.Projects.Where(p => p.Name == "Overhead"))
{
project.IsActive = true;
}
That's using LINQ for the querying part, which is appropriate as that's what the Q of LINQ stands for. I'd strongly urge you not to mutate items within LINQ calls in the way that Mayank's answer does though. It's error-prone (as evidenced by the original answer not working) and against the spirit of LINQ.
That's about as readable as it gets, IMO. It does exactly the same thing as the original code, mind you - you can't avoid something iterating over every item in the list, if every item might be one you want to update.
EDIT: Just for laughs, if you really, really wanted to do it in pretty minimal code, you could use:
// DON'T USE THIS!
MyProjects.Project.Count(p => p.Name == "Overhead" && (p.IsActive = true));
Here we use the fact that && is short-circuiting to avoid evaluating the assignment (p.IsActive = true) unless the condition is matched. It's handy that we're assigning a bool value to a property, as that means we don't need to do anything else to make it a valid second operand for the && operator. We use Count() to fully evaluate the result without creating any additional lists etc - and we use the version with a predicate to avoid even needing a Where call, which a previous version did. (LastOrDefault would work too.) But it's all a horrible abuse, and should never appear in any real code.

I've come up with a way to get it down to one line, without abusing LINQ, since I'm only using it for the querying part (filter), and using a custom extension method to perform the property setting action. You're still going to enumerate the items (you have to) but you can hide that away in the extension method. I suspect that you didn't really care whether you enumerated the item or not, you just didn't like the amount of visible space a foreach loop would take up in your main code.
Use this extension method:
public static IEnumerable<T> SetProperty<T>(this IEnumerable<T> list, Action<T> action)
{
foreach (var item in list)
{
action.Invoke(item);
}
return list;
}
This allows you to get it down to one readable line.
Projects.Where(p => p.Name == "Overhead").SetProperty(p => p.IsActive = true);
Complete test program:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var Projects = new List<Project>() {
new Project() { Name="Overhead", IsActive=false },
new Project() { Name="Nadfadfs", IsActive=false },
new Project() { Name="Overhead", IsActive=false },
new Project() { Name="dasfasdf", IsActive=false }
};
PrintProjectList(Projects);
Console.WriteLine("--Setting property--");
Projects.Where(p => p.Name == "Overhead").SetProperty(p => p.IsActive = true);
PrintProjectList(Projects);
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
static void PrintProjectList(IEnumerable<Project> projects)
{
foreach(var p in projects)
{
Console.WriteLine($"Name: {p.Name} IsActive: {p.IsActive}");
}
}
}
class Project
{
public string Name { get; set; }
public bool IsActive { get; set; }
}
public static class Extensions
{
public static IEnumerable<T> SetProperty<T>(this IEnumerable<T> list, Action<T> action)
{
foreach (var item in list)
{
action.Invoke(item);
}
return list;
}
}
}
Output:
Name: Overhead IsActive: False
Name: Nadfadfs IsActive: False
Name: Overhead IsActive: False
Name: dasfasdf IsActive: False
--Setting Property--
Name: Overhead IsActive: True
Name: Overhead IsActive: False
Name: Overhead IsActive: True
Name: Overhead IsActive: False
It turns out that my SetProperty function is very similar to the ForEach that's already built into the framework. The main difference being that mine can operate on any IEnumerable<T>. That syntax is loved by some, and hated by others, for reasons that Eric Lippert pointed out on his blog (Thanks to Jon Skeet for pointing this out). Also, see this discussion by the Microsoft team. I'll leave it to you to draw your own conclusion.
On a side note, calling it SetProperty is kind of inaccurate, because you could do any action on the items in the collection. You could call it ForEach, but that clashes with the framework. Not positive what I'd call it, but perhaps PerformAction.

Following Linq statement should work
MyProjects.Projects.Where(p => p.Name == "Overhead")
.Select(x => {x.IsActive = true; return x;})
.ToList();
As per comments from #JonSkeet, #TimSchmelter and #LeandroSoares, above code is really bad idea. Here are some reasons
Calling .ToList() causes whole lazy collection to be executed and loaded in the memory.
Code above isn't very readable and hard to maintain.
Code above is abusing the API as it is forcing the API to do things which it is not designed for.

Related

Reassign object in foreach loop c#

Not sure I understand why I can do this with a for loop and not a foreach loop?
This is the code that works. Looping through a BindingList Products, finding a match and then assigning that product at index i to the new product that's passed in.
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
for (int i = 0; i < Products.Count; i++)
{
if (Products[i].ProductID == productToUpdateID)
{
Products[i] = productToUpdate;
}
}
}
If I try to do this with a foreach loop I get an error that I cannot assign to the iterator variable. What is the reasoning for this and is there a way to get around it or is using a for loop for this kind of problem the best solution?
This is essentially what I'm trying to do.
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
foreach(Product product in Products)
{
if (product.ProductID == productToUpdateID)
{
product = productToUpdate;
}
}
}
I can do something like this and reassign all the properties explicitly but want to see if there is another way to do it.
foreach(Product product in Products)
{
if (product.ProductID == productToUpdateID)
{
product.Name = productToUpdate.Name;
}
}
Thanks!
The foreach construct is for when you want to do something with each item in the list. That does not seem to be what you are doing. You are modifying the list itself, by removing an item and replacing it.
Personally I would not use a loop at all, I'd just remove the old item and add the new one.
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
Products.RemoveAll( x => x.ProductID == productToUpdateID );
Products.Add( productToUpdate );
}
Or if you wish to preserve order:
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
var index = Products.FindIndex( x => x.ProductID == productToUpdateID );
Products[index] = productToUpdate;
}
The reasons have already been given, but as a minor detail: this is sometimes possible; there is an alternative syntax in recent C# that uses a ref-local for the iterator value:
foreach (ref [readonly] SomeType value in source)
which is only available for some scenarios - naked arrays, spans, or custom iterator types with a ref-return Current - and as long as the optional readonly modifier is not used, you can assign directly via the value variable, since this is a direct reference to the underlying source. The uses for this are rare and niche. If Products is a List<T>, you could combine this with CollectionMarshal.AsSpan(...) to achieve what you want, but frankly I'd consider that hacky (apart from other things, it would bypass the list's internal change protections). Basically: don't do this, but : it isn't entirely impossible.
The foreach loop iterates over the elements of a collection, and the iteration variable is simply a reference to the current element in the collection.
The reason you cannot modify the iteration variable itself is that it is a read-only reference to the element in the collection. Modifying the iteration variable would not change the element in the collection; it would only change the reference.
Alternative ways are already mentioned in the above answers.
Just for the record. IMHO the best way is to use a foreach loop with a modified code like this. It only makes one iteration
int i=-1;
foreach (var product in products )
{
i++;
if (product.ProductID == productToUpdate.ProductID)
{
products[i]=productToUpdate;
break;
}
}
But if you want to use linq for some reason, you can do it in one line
products = products.Select(x => x = x.ProductID == productToUpdate.ProductID?productToUpdate:x).ToList();

Remove elements from one list that are found in another

This should be quite easy, however I am failing to see why all my methods are not working.
I have looked at all the solutions and used them appropriately however am not getting the result.
solutions include
Solution 1
Solution 2
Here is the code:
IEnumerable<feature> available = _repo.GetAvailableFeatures();
IEnumerable<feature> selected = _repo.GetSelectedFeatures();
Using Except
var filteredList = (available.Except(selected)).ToList;
Using Linq
var availableList = available.ToList();
var selectedList = selected.ToList();
availableList.RemoveAll(item => selectedList.Contains(item));
Using old fashion for
for (var i = 0; i < availableList.Count - 1; i++)
{
foreach (var t in selectedList)
{
if (availableList[i].Id == t.Id)
{
availableList.RemoveAt(i);
}
}
}
My Feature class looks like this:
public class Feature
{
public int Id;
public int Desc;
}
Can anyone see it my mistakes here?
When you use Except you need to define what "equal" means for the feature type, otherwise reference equality (are they the same object) is used by default. In your loop you define "equal" as "Ids are equal", so some options are:
Override Equals and GetHashCode in the feature class
This becomes the "default" definition of equal for the type
Define a class that implements IEqualityComparer<feature>
This could be used only when that definition is needed
Use Where instead of Except:
var filteredList = available.Where(a => !selected.Any(s => s.Id == a.Id))
.ToList();
Performance is sub-optimal but it is a simpler code solution if performance overall is not affected significantly.
This looks like a straightforward process of comparing the two data lists to each other and removing them in a standard fashion. I would personally go with a List setup instead of IEnumerable.
List<Feature> availableList = _repo.GetAvailableFeatures();
List<Feature> selectedList = _repo.GetSelectedFeatures();
foreach(Feature avail in availableList){
if(selectedList.Contains(avail)){
selectedList.Remove(avail)
}

Change a list's property using linq

How to make the following code shorter, perhaps using anonymous method or extensions and LINQ.
Since I have to repeat this code several times and I want to make it as succinct as possible.
var imagesToUnlock = App.ImageListVM.Items.Where(img => img.Category == key);
foreach (var image in imagesToUnlock)
{
image.IsLocked = false;
}
The other solutions here feel dirty because they mutate objects in a collection via the use of LINQ.
I would instead, put the code and the filter condition into an extension method and call that:
public static IEnumerable<Item> UnlockWhere(this IEnumerable<Item> list, Func<Item, bool> condition) {
foreach (var image in list)
if (condition(image)) {
image.IsLocked = false;
yield return image;
}
}
The keeps the immutability-concerns of LINQ intact and still produces the expected result.
The call becomes:
var unlockedItems = App.ImageListVM.Items.UnlockWhere(img => img.Category == key);
EDIT
Re-written to completely remove LINQ. Instead, this new method iterates only once and returns a new, mutated collection.
Not the most efficient way to do it, but I believe you can do
var imagesToUnlock = App.ImageListVM.Items.Where(img => img.Category == key).ToList().Foreach(f => f.IsLocked = false);
Check out the Foreach method on List<T> for more info.
I would also like to note (as some have pointed out in the comments) that this is not considered best practice by some people. You should take a look at this article by Eric Lippert, who explains the issue in better detail.
Here's a stab as an extension method
Code
public static IEnumerable<T> SetPropertyValues<T>(this IEnumerable<T> items, Action<T> action)
{
foreach (var item in items)
{
action(item);
yield return item;
}
}
Usage
private class Foo
{
public string Bar { get; set; }
}
[TestMethod]
public void SetPropertyValuesForMiscTests()
{
var foos = new[] { new Foo { Bar = "hi" }, new Foo { Bar = "hello" } };
var newList = foos.SetPropertyValues(f => f.Bar = "bye");
Assert.AreEqual("bye", newList.ElementAt(0).Bar);
Assert.AreEqual("bye", newList.ElementAt(1).Bar);
}
I tested it and it works fine.
Yeah you can do this. Adapted from this answer.
imagesToUnlock.Select(i => {i.IsLocked = false; return i;}).ToList();
Edit: A lot of people are saying this is bad practice. I agree with dasblinkenlight here.. Exploring the limits of LINQ and C# is our duty as programmers. It isn't unreasonable to change the objects type from the DTO to the view model or domain object, I know its not the best, but if encapsulated and commented it isn't the end of the world to use select to do this. But please be conscious of the best practices explained by Eric.

Exception when modifying collection in foreach loop

I know the basic principle of not modifying collection inside a foreach, that's why I did something like this:
public void UpdateCoverages(Dictionary<PlayerID, double> coverages)
{
// TODO: temp
var keys = coverages.Select(pair => pair.Key);
foreach (var key in keys)
{
coverages[key] = 0.84;
}
}
And:
class PlayerID : IEquatable<PlayerID>
{
public PlayerID(byte value)
{
Value = value;
}
public byte Value { get; private set; }
public bool Equals(PlayerID other)
{
return Value == other.Value;
}
}
First I save all my keys not to have the Collection modified exception and then I go through it. But I still get the exception which I cannot understand.
How to correct this and what is causing the problem?
First I save all my keys
No you don't; keys is a live sequence that is actively iterating the collection as it is iterated by the foreach. To create an isolated copy of the keys, you need to add .ToList() (or similar) to the end:
var keys = coverages.Select(pair => pair.Key).ToList();
Although personally I'd probably go for:
var keys = new PlayerID[coverages.Count];
coverages.Keys.CopyTo(keys, 0);
(which allows for correct-length allocation, and memory-copy)
What is a live sequence actually?
The Select method creates non-buffered spooling iterator over another... that is a really complicated thing to understand, but basically: when you first start iterating var key in keys, it grabs the inner sequence of coverages (aka coverages.GetEnumerator()), and then every time the foreach asks for the next item, it asks for the next item. Yeah, that sounds complicated. The good news is the C# compiler has it all built in automatically, with it generating state machines etc for you. All mainly done using the yield return syntax. Jon Skeet gives an excellent discussion of this in Chapter 6 of C# in Depth. IIRC this used to be the "free chapter", but now it is not.
However, consider the following:
static IEnumerable<int> OneTwoOneTwoForever()
{
while(true) {
yield return 1;
yield return 2;
}
}
It might surprise you to learn that you can consume the above, using the same non-buffered "when you ask for another value, it runs just enough code to give you the next value" approach:
var firstTwenty = OneTwoOneTwoForever().Take(20).ToList(); // works!

In a generic list, is there a way to copy one property to another in a declarative /LINQ manner?

I have a class with two properties, say
public class Book {
public string TitleSource { get; set; }
public string TitleTarget { get; set; }
}
I have an IList<Book> where the TitleTarget is null and for each item in the list, I need to copy the TitleSource property to the TitleTarget property. I could do this through a loop, sure, but it seems like there's a LINQ or nice declarative way to do this. Is there?
Linq was designed as a way to consume things. If you look at web discussions about why there is no IEnumerable.ForEach(...) extension, you'll see that the Linq designers purposefully avoided Linq to Object scenarios where the methods were designed to change object values.
That said, you can cheat by "selecting" values and not using the results. But, that creates items which are thrown away. So, a foreach loop is much more efficient.
Edit for people who really want something besides foreach
Another "cheat" that wouldn't produce a new list would be to use a method that does little work of it's own, like Aggregate, All, or Any.
// Return true so All will go through the whole list.
books.All(book => { book.TitleTarget = book.TitleSource; return true; });
It's not LINQ as such, but there's:
books.Where(book => book.TitleTarget == null).ToList()
.ForEach(book => book.TitleTarget = book.TitleSource);
The main point is the ToList method call: there's no ForEach extension method (I don't think?) but there is one on List<T> directly. It wouldn't be hard to write your own ForEach extension method as well.
As to whether this would be better than a simple foreach loop, I'm not so sure. I would personally choose the foreach loop, since it makes the intention (that you want to modify the collection) a bit clearer.
#John Fisher is correct, there is no IEnumerable.ForEach.
There is however a ForEach on List<T>. So you could do the following:
List<Book> books = GetBooks();
books.ForEach(b => b.TitleTarget = b.TitleSource);
If you wanted a IEnumerable.ForEach it would be easy to create one:
public static class LinqExtensions
{
public static void ForEach<TSource>(this IEnumerable<TSource> source, Action<TSource> action)
{
foreach (var item in source)
{
action(item);
}
}
}
You can then use the following snippet to perform your action across your collection:
IList<Book> books = GetBooks();
books.ForEach(b => b.TitleTarget = b.TitleSource);
If you can use .NET 4.0, and you are using a thread-safe collection then you can use the new parallel ForEach construct:
using System.Threading.Tasks;
...
Parallel.ForEach(
books.Where(book => book.TitleTarget == null),
book => book.TitleTarget = book.TitleSource);
This will queue tasks to be run on the thread pool - one task that will execute the assignment delegate for each book in the collection.
For large data sets this may give a performance boost, but for smaller sets may actually be slower, given the overhead of managing the thread synchronization.
books.Select(b => b.TitleTarget = b.TitleSource);
This doesn't create any 'new items', just a query that you won't enumerate. That doesn't seem like a big deal to me.

Categories

Resources