LINQ expression instead of nested foreach loops - c#

I have these two clases:
public class Client{
public List<Address> addressList{get;set;}
}
public class Address{
public string name { get; set; }
}
and I have a List of type Client called testList. It contains n clients and each one of those contains n addresses
List<Client> testList;
how can i do the following using LINQ:
foreach (var element in testList)
{
foreach (var add in element.addressList)
{
console.writeLine(add.name);
}
}

Well I wouldn't put the Console.WriteLine in a lambda expression, but you can use SelectMany to avoid the nesting:
foreach (var add in testList.SelectMany(x => x.addressList))
{
Console.WriteLine(add.name);
}
I see little reason to convert the results to a list and then use List<T>.ForEach when there's a perfectly good foreach loop as part of the language. It's not like you naturally have a delegate to apply to each name, e.g. as a method parameter - you're always just writing to the console. See Eric Lippert's blog post on the topic for more thoughts.
(I'd also strongly recommend that you start following .NET naming conventions, but that's a different matter.)

foreach(var a in testList.SelectMany(c => c.addressList))
{
Console.WriteLine(a.name);
}
It will not materialize any new collection.

This may helps:
testList.ForEach(i => i.addressList.ForEach(j => Console.WriteLine(j.name)));

foreach(var add in testList.SelectMany(element => element.addressList)){
Console.WriteLine(add.name);
}

testList.SelectMany(c => c.addressList)
.Select(a => a.name)
.ToList()
.ForEach(Console.WriteLine)

Use the ForEach method:
testList.ForEach(tl=>tl.addressList.ForEach(al=>console.writeLine(al.name)));

LINQ doesn't include a ForEach function, and they don't intend to, since it goes against the idea of LINQ being functional methods. So you can't do this in a single statement. List<T> has a ForEach method, but I'd recommend not using this for the same reasons that it's not in LINQ.
You can, however, use LINQ to simplify your code, e.g.
foreach (var add in testList.SelectMany(x => x.addressList))
{
Console.WriteLine(add.name);
}
// or
foreach (var name in testList.SelectMany(x => x.addressList).Select(x => x.name))
{
Console.WriteLine(name);
}

Related

Advise to optimize the following code in a better way

I have some data stored in a dictionary where the values are basically a list of objects with few attributes in them. Right now I'm looping through as following to get the data stored in a specific attribute. These data are then added into a drop down list (unity UI dropdown)
foreach (KeyValuePair<string, List<NameIDValuePair>> kvp in TeamValuePair)
{
List<NameIDValuePair> list = kvp.Value;
if(kvp.Key == teamNames.options[teamNames.value].text)
{
foreach (var rec in list)
{
screenNamesDropDown.options.Add(new TMP_Dropdown.OptionData { text = rec.ScreenName });
}
}
}
teamNames and screenNamesDropDown are dropdown elements part of my unity UI.
The structure of the NameIdValuePair looks as follows:
public class NameIdValuePair
{
public string ScreenName { get; private set; }
public string ScreenId { get; private set; }
}
I would like to optimize this piece of code in a better way using linq - so that it's a bit more readable. Since I'm pretty new to linq, i'm not really sure if I'm using the right keywords when searching for suggestions but so far I haven't had much success in finding any helpful suggestion.
Thanks
As mentioned before instead of looping a Dictionary - where we already know that the keys are unique - you could simply use Dictionary.TryGetValue
// do this only once!
var key = teamNames.options[teamNames.value].text;
if (TeamValuePair.TryGetValue(key, out var list))
{
foreach(var item in list)
{
screenNamesDropDown.options.Add(new TMP_Dropdown.OptionData(item.ScreenName));
}
}
and then actually the only place where you could use Linq if you really want to would maybe be in
var key = teamNames.options[teamNames.value].text;
if (TeamValuePair.TryGetValue(key, out var list))
{
screenNamesDropDown.options.AddRange(list.Select(item => new TMP_Dropdown.OptionData(item.ScreenName)));
}
if this makes it better to read is questionable though.
And in general the question would also be if you always want to Add (AddRange) to the screenNamesDropDown.options or if you maybe want to actually replace the options. Then instead of AddRange you could do
screenNamesDropDown.options = list.Select(item => new TMP_Dropdown.OptionData(item.ScreenName)).ToList();

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.

How to query this in LINQ?

I'm trying to write a query in LINQ and, so far, I'm unable to make it work. If I've managed to ask the most obvious LINQ question in history then I apologize but I really do need some help with this one ...
Here is the gist of what I'm trying to do:
I have a class for a keyword:
class Keyword
{
public string Name {get; set;}
}
I also have a class for a file:
class File
{
public IList<Keyword> Keywords { get; set;}
}
Now, assuming I have a method to do a search for files by keyword(s):
IEnumerable<File> FindByKeywords(IEnumerable<Keyword> keywords)
{
// Let's say that Context.Files is a collection of File objects
// each of which contains a collection of associated keywords
// that may (or may not) match the keywords we get passed as
// a parameter. This is where I need LINQ magic to happen.
return Context.Files; // How do I select the files by the list of keywords?
}
I've seen examples of using Contains on the passed in list of keywords but that only seems to work for instances where the matching property is a scalar. In my case the matching property is another list of keywords.
In other words, this doesn't work:
IEnumerable<File> FindByKeywords(IEnumerable<Keyword> keywords)
{
return Context.Files.Where(x => keywords.Contains(x);
}
Anyone have any ideas? I really just need to find files that contain one or more keywords that match whatever is in the list of keywords passed in as a parameter. It's probably got an obvious solution but I can't see it.
Thanks in advance.
Do you want to find all File objects, for which any of the elements in Keywords are present in the collection of keywords you passed into the method?
Doing anything with the Keyword class inside your query is apparently a no-no. As your error suggests, it can only translate primitive types.
var names = keywords.Select(x => x.Name).ToList();
return Context.Files.Where(x => keywords.Select(y => y.Name).Intersect(names).Any());
Maybe since you are having trouble wrapping your head around how to do this with Linq, you should start with something you can do. Some simple loops for example. Once you have that working, you can move on to Linq queries.
IEnumerable<File> FindByKeywords(IEnumerable<Keyword> keywords)
{
var foundFiles = new List<File>();
foreach (File file in Context.Files)
{
foreach (string fileWord in file)
{
foreach (string keyword in keywords)
{
if (fileWord == keyword)
{
foundFiles.Add(file);
break;
}
}
}
}
return foundFiles;
}
I might build an index first, then query the index:
IDictionary<string, List<File>> BuildIndex(IEnumerable<File> files)
{
var index = new Dictionary<string, List<File>>();
foreach (File file in files)
{
foreach (string keyword in file.Keywords.Select(k => k.Name))
{
if (!index.ContainsKey(keyword))
index[keyword] = new List<File>();
index[keyword].Add(file);
}
}
return index;
}
IEnumerable<File> FindByKeywords(IEnumerable<Keyword> keywords)
{
var index = BuildIndex(Context.Files);
return keywords
.Where(k => index.ContainsKey(k.Name))
.SelectMany(k => index[k.Name])
.Distinct()
.ToList();
}
As I understand it, you're looking for all the files where any of their keywords is in the collection of keywords.
I'd write it like this:
IEnumerable<File> FilesThatContainsAnyKeyword(
IEnumerable<File> files, // IQueryable<File>?
IEnumerable<Keyword> keywords)
{
var keywordSet = new HashSet<string>(keywords.Select(k => k.Name));
return files.Where(f =>
f.Keywords.Any(k => keywordSet.Contains(k.Name))
);
}
Then call it:
IEnumerable<File> FindByKeywords(IEnumerable<Keyword> keywords)
{
return FilesThatContainsAnyKeyword(Context.Files, keywords);
}
Since your keyword objects cannot be compared for equality directly, you have to compare them by identity (their Name).

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.

Treat multiple IEnumerable objects as a single IEnumerable

My question seems to be something easy, but I can't figure it out.
Let's say I have a "root" IEnumerable of objects. Each object has IEnumerable of strings. How can I obtain a single IEnumerable of those strings?
A possible solution is to do:
public IEnumerable<string> DoExample()
{
foreach (var c in rootSetOfObjects)
{
foreach (var n in c.childSetOfStrings)
{
yield return n;
}
}
}
But maybe there is a magic solution with Linq?
rootSetOfObjects.SelectMany(o => o.childSetOfStrings)
there is SelectMany in Linq that should work for you:
http://msdn.microsoft.com/en-us/vcsharp/aa336758.aspx#SelectManyCompoundfrom1
it definitely works on your collection and compound collections

Categories

Resources