Aggregate methods to use with IEnumerable lists - c#

Is there a way to "convert" (return) an IEnumerable list of, e.g., strings to an IEnumerable list of a different type when that different type accepts the former type in its constructor?
For example, the DataTable.Columns.AddRange() method accepts only lists of columns. Is there a way to return a DataColumn list by offering a string list using LINQ or some sort of aggregate function? I imagine the code would roughly do the following, but in one line:
var columnList = new List<DataColumn>();
foreach (var item in myStringList)
{
columnList.Add(item);
}
return columnList;
Likewise, is there an aggregate method that will take a list and run each of its members against a specific method? For example, I am looking for a one line way to perform the following similar foreach loop:
foreach (var item in myStringList)
{
myDataTable.Columns.Add(item);
}
Obviously, I am looking for generic answers that are not actually dependent on data columns or strings.

You can write
var newList = list.ConvertAll(x => new Something(x));
list.ForEach(x => DoSomething(x));
These methods are defined by th List<T> class.
If you have an arbitrary IEnumerable<T>, you can use LINQ:
var newEnumerable = enumerable.Select(x => new Something(x));

Call Enumerable.Aggregate
List<DataColumn> result = myStringList.Aggregate(
new List<DataColumn>(),
(list, item) => { list.Add(item); return list; }
);
return result;
That said, foreach statement is better.

Yes, in fact, although not all of them are LINQ specific. ForEach is just a List method. For your two examples:
myStringList.ForEach(x => columnList.Add(x));
// assumes myStringList is a List<T>... otherwise convert your enumerable using ToList()
The ForEach method takes an Action and lets you perform some logic on each item. So if you want to do transformations, it's easy enough combining with select:
myStringList.Select(x => new DataColumn(x))
.ToList()
.ForEach(x => columnList.Add(x));
// transforms each element of the string by adding some text, then calling foreach
// on the items

myStringList.ForEach(item => myDataTable.Columns.Add(item));
EDIT: that's not Linq. Sorry, my mistake.

Related

Use a foreach loop within a foreach loop in a linq query

I am wondering on how to do the following. I have the Linq query:
Items items.Where(i => i.GetType() == typeof(SubItem))
.Cast<SubItem>()
.ToList()
.ForEach(i => i.SomeList.Add(i.SomeObject.ForEach(i => i.SomeString)));
My question is about i.SomeList.Add(). I want to return a couple of string values to i.SomeList.Add() from i.SomeObject but I do not know how I can do this in this way? Is it even possible like this to have another ForEach Loop within a Linq ForEach usinq Linq query?
I believe this foreach loop will achieve your goal, if I've understood the problem.
It will loop over any element of items that is a (or is derived from) SubItem. It will then select all SomeObject.SomeString strings and add them to the SomeList.
foreach (var subItem in items.OfType<SubItem>()) {
subItem.SomeList.AddRange(subItem.SomeObject.Select(o => o.SomeString));
}
This is a compilation of suggestions from Panagiotis Kanavos, juharr, and Aluan Haddad.
LINQ isn't really for running Add operations... it's much more powerful when you think of it as returning a resultset.
So instead of
//Add every value of SomeField to targetList
sourceList.ForEach( x => targetList.Add(x.SomeField) )
Think of doing it this way:
//Create a list of all instances of SomeField and assign it to targetList
targetList = sourceList.Select( x => x.SomeField).ToList();
Or if you need to keep the existing items in the target list, do this:
//Create a list of all SomeFields and add it to targetList
targetList.AddRange
(
sourceList.Select( x => x.SomeField )
);
Similarly, instead of using a nested foreach, consider using SelectMany.
I'm not completely clear on your requirements but you probably want something like this:
//To SomeList, add the SomeString field from all instances of SomeObject
someList.AddRange
(
items.OfType<SubItem>().SelectMany( x => x.SomeObject ).Select( x => x.SomeString )
);

List<T> Concatenation dynamically

I am trying to concate List<> as follows-
List<Student> finalList = new List<Student>();
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.Concat(ListFromDict);
}
But no concatenation happens. finalList remains empty. Any help?
A call to Concat does not modify the original list, instead it returns a new list - or to be totally accurate: it returns an IEnumerable<string> that will produce the contents of both lists concatenated, without modifying either of them.
You probably want to use AddRange which does what you want:
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.AddRange(ListFromDict);
Or even shorter (in one line of code):
finalList.AddRange((List<Student>)entry.Value);
And because entry.Value is already of type List<Student>, you can use just this:
finalList.AddRange(entry.Value);
Other answers have explained why Concat isn't helping you - but they've all kept your original loop. There's no need for that - LINQ has you covered:
List<Student> finalList = dictOfList.OrderBy(k => k.Key)
.SelectMany(pair => pair.Value)
.ToList();
To be clear, this replaces the whole of your existing code, not just the body of the loop.
Much simpler :) Whenever you find yourself using a foreach loop which does nothing but build another collection, it's worth seeing whether you can eliminate that loop using LINQ.
You may want to read up the documentation on Enumerable.Concat:
Return Value
Type: System.Collections.Generic.IEnumerable
An IEnumerable that contains the concatenated elements of the two input sequences.
So you may want to use the return value, which holds the new elements.
As an alternative, you can use List.AddRange, which Adds the elements of the specified collection to the end of the List.
As an aside, you can also achieve your goal with a simple LINQ query:
var finalList = dictOfList.OrderBy(k => k.Key)
.SelectMany(k => k.Value)
.ToList();
As specified here, Concat generates a new sequence whereas AddRange actually adds the elements to the list. You thus should rewrite it to:
List<Student> finalList = new List<Student>();
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.AddRange(ListFromDict);
}
Furthermore you can improve the efficiency a bit, by omitting the cast to a List<T> object since entry.Value is already a List<T> (and technically only needs to be an IEnumerable<T>):
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
finalList.AddRange(entry.Value);
}
Concat method does not modify original collection, instead it returns brand new collection with concatenation result. So, either try finalList = finalList.Concat(ListFromDict) or use AddRange method which modifies target list.

How to modify only one or two field(s) in LINQ projections?

I have this LINQ query:
List<Customers> customers = customerManager.GetCustomers();
return customers.Select(i => new Customer {
FullName = i.FullName,
Birthday = i.Birthday,
Score = i.Score,
// Here, I've got more fields to fill
IsVip = DetermineVip(i.Score)
}).ToList();
In other words, I only want one or two fields of the list of the customers to be modified based on a condition, in my business method. I've got two ways to do this,
Using for...each loop, to loop over customers and modify that field (imperative approach)
Using LINQ projection (declarative approach)
Is there any technique to be used in LINQ query, to only modify one property in projection? For example, something like:
return customers.Select(i => new Customer {
result = i // telling LINQ to fill other properties as it is
IsVip = DetermineVip(i.Score) // then modifying this one property
}).ToList();
you can use
return customers.Select(i => {
i.IsVip = DetermineVip(i.Score);
return i;
}).ToList();
Contrary to other answers, you can modify the source content within linq by calling a method in the Select statement (note that this is not supported by EF although that shouldn't be a problem for you).
return customers.Select(customer =>
{
customer.FullName = "foo";
return customer;
});
You "can", if you create a copy constructor, which initializes a new object with the values of an existing object:
partial class Customer
{
public Customer(Customer original)
{
this.FullName = original.FullName;
//...
}
}
Then you can do:
return customers.Select(i => new Customer(i) { IsVip = DetermineVip(i.Score)})
.ToList()
But the downfall here is you will be creating a new Customer object based on each existing object, and not modifying the existing object - this is why I have put "can" in quotes. I do not know if this is truly what you desire.
No, Linq was designed to iterate over collections without affecting the contents of the source enumerable.
You can however create your own method for iterating and mutating the collection:
public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
foreach(T item in enumeration)
{
action(item);
}
}
You can then use as follows:
return customers.ToList()
.ForEach(i => i.IsVip = DetermineVip(i.Score))
.ToList();
Note that the first ForEach will clone the source list.
As customers already is a List, you can use the ForEach method:
customers.ForEach(c => c.IsVip = DetermineVip(c.Score));

Possible to order a dynamic type?

Say I have the code below:
dynamic myData = GetMyData();
foreach(dynamic d in myData.data)
{
Console.WriteLine(d.name);
}
How could I writeout all of the names in alphabetical order? If I were using something like List<MyClass> i would just use myData.OrderBy(t => t.name), but this does not seem to work when I'm using a dynamic type.
Any suggestions to how I can order these values?
Enumerable.OrderBy(myData, (Func<dynamic, dynamic>)(t => t.name));
That should return the same as myData.OrderBy(t => t.name) would normally.
Since OrderBy is an extension method, it won't work on dynamic types. See this answer.
This might work for you:
IEnumerable<dynamic> sequence = Enumerable.Cast<dynamic>(myData);
foreach (var result in sequence.OrderBy(x => x.name))
{
Console.WriteLine(result.name);
}
Basically after the call to Cast<dynamic> and the conversion to IEnumerable<dynamic>, you can do what you like as a sequence rather than a single value.
Not that I've actually tried the above, but I believe it should work.

C# list question

What would be the nice way to make the following action:
List<IEnumerable<T>> listOfEnumerables = Get...();
List<T> listOfObjects = new List<T>();
// I want 'listOfObjects' to contain every element from every enumerable
// in 'listOfEnumerables'.
Is there any beautiful way to make this instead of the following:
foreach (var enumerable in listOfEnumerables)
{
listOfObjects.AddRange(enumerable);
}
Thank you.
You can use LINQ:
List<T> listOfObjects = listOfEnumerables.SelectMany(s => s).ToList();
listOfEnumerables.ForEach(i => listOfObjects.AddRange(i));

Categories

Resources