James Hill
James Hill

Reputation: 61812

List<T>.ForEach with index

I'm trying to find the LINQ equivalent of the following code:

NameValueCollection nvc = new NameValueCollection();

List<BusinessLogic.Donation> donations = new List<BusinessLogic.Donation>();
donations.Add(new BusinessLogic.Donation(0, "", "", "");
donations.Add(new BusinessLogic.Donation(0, "", "", "");
donations.Add(new BusinessLogic.Donation(0, "", "", "");

for(var i = 0; i < donations.Count(); i++)
{
    // NOTE: item_number_ + i - I need to be able to do this
    nvc.Add("item_number_" + i, donations[i].AccountName);
}

I was hoping I could use something like:

NameValueCollection nvc = new NameValueCollection();

List<BusinessLogic.Donation> donations = new List<BusinessLogic.Donation>();
donations.Add(new BusinessLogic.Donation(0, "", "", "");
donations.Add(new BusinessLogic.Donation(0, "", "", "");
donations.Add(new BusinessLogic.Donation(0, "", "", "");

donations.ForEach(x => nvc.Add("item_name_" + ??, x.AccountName);

But I've not found a way to determine which iteration the loop is on. Any help would be appreciated!

Upvotes: 34

Views: 101749

Answers (12)

BOT Alex
BOT Alex

Reputation: 47

I strongly recommend against this method, unless you're trolling your friends.

This is a way to create a "For" loop with Linq, but with MANY downsides:

int[] arr = new int[] {1, 1, 1, 1};

Enumerable.Range(0, arr.length)
    .ToList()
    .ForEach(i => 
        {
            arr[i] += 2;
        });

The array should now be {3, 3, 3, 3}.

Again, do not use this!

Some of these downsides are (but not limited to):

  • Really slow performance
  • Large amount of memory usage
  • Unreadable

Here's an example of how unreadable it can be: Unreadable example

I made this so my friend wouldn't mess with my code.

The code creates a "for" loop that goes from 0.00 to 1.00 at an 0.01 increment and then "bakes" the curve.

Upvotes: -2

Jason C
Jason C

Reputation: 40336

C# 7 (circa 2017) added a cleaner tuple syntax, which can be used with the indexed form of Select (there's no need to compute the index manually) to give a nice concise syntax. E.g.:

foreach ((var item, int n) in TheItems.Select((i,n)=>(i,n))) {
    // item is the current item, n is its index
}

Or if you prefer:

foreach (var element in TheItems.Select((item,n)=>(item,n))) {
    // element.item is the current item, element.n is its index
}

So in the OP's case, for example:

foreach ((var item, int n) in donations.Select((i,n)=>(i,n)))
    nvc.Add($"item_number{n}", item.AccountName);

Or, if you prefer:

foreach ((var k, var v) in donations.Select((i,n)=>($"item_number_{n}",i.AccountName)))
    nvc.Add(k, v);

Or, similar to this answer but slightly less verbose, but note that this will kill lazy evaluation by calling ToList(), which forces enumeration over the whole source prior to calling ForEach():

TheItems.Select((item,n)=>(item,n)).ToList().ForEach(element => { 
    // element.item is the current item, element.n is its index
});

Upvotes: -2

John
John

Reputation: 6278

A crappy solution would be to simply use select and return a useless value.

items.toList().Select((el, index) =>
{
    el.doStuff();
    return 0;
});

Upvotes: -3

Soroush Falahati
Soroush Falahati

Reputation: 2327

This can be done using aggregate as well. Check the following example:

var data = new[]
{
    "A",
    "B",
    "C",
    "D"
};

var count = data.Aggregate(0, (index, item) =>
{
    Console.WriteLine("[{0}] => '{1}'", index, item);

    return index + 1;
});

Console.WriteLine("Total items: {0}", count);

Aggregate here acts as a for statement. The only downside is that you need to increase the value of the index by one in each iteration and return it.

Upvotes: 4

Patrick Ribbing
Patrick Ribbing

Reputation: 227

This is an old post but is highly ranked by Google so I figured a more generic approach would be suitable. (Also, I tend to forget how this is done which means I have to google every time...)

Assume a list of integers:

var values = new List<int>() { 2, 3, 4, 0x100, 74, 0xFFFF, 0x0F0F };

To iterate the list and having an index do the following:

values.Select((x, i) => new
{
    item = x,
    index = i
})
.ToList()
.ForEach(obj =>
{
    int value = obj.item;
    int valueIndex = obj.index;
});

Upvotes: 16

an.dr.eas.k
an.dr.eas.k

Reputation: 157

I like to do that the following way:

NameValueCollection nvc = new NameValueCollection();

List<BusinessLogic.Donation> donations = new List<BusinessLogic.Donation>();
donations.Add(new BusinessLogic.Donation(0, "", "", ""));
donations.Add(new BusinessLogic.Donation(0, "", "", ""));
donations.Add(new BusinessLogic.Donation(0, "", "", ""));

Enumerable
    .Range(0, donations.Count())
    .ToList()
    .ForEach(i => nvc.Add("item_number_" + i, donations[i].AccountName));

Upvotes: 0

lc.
lc.

Reputation: 116498

It's a little convoluted and creates an intermediate collection, but how about:

donations.Select((x, i) => new {Name = "item_name_" + i, x.AccountName})
    .ToList()
    .ForEach(x=> nvc.Add(x.Name, x.AccountName));

This uses the overload of Enumerable.Select which incorporates the index.

I do have to argue there is nothing really to gain from doing it this way. You create more overhead with the intermediate collection and IMHO lose readability over your original for-loop.

You can also skip the intermediate collection if you're willing to use foreach loop instead of List.ForEach. See @wageoghe's answer (again highly recommended).

Upvotes: 16

Tiago Raposo
Tiago Raposo

Reputation: 309

If you really want to use a List.ForEach, it's simple:

//[...]
int i=0;
donations.ForEach(x => nvc.Add("item_name_" + i++, x.AccountName);

Upvotes: 30

wageoghe
wageoghe

Reputation: 27608

Piggybacking on the answer by @lc.

foreach (var x in donations.Select((d, i) => new {ItemName = "item_name_" + i, AccountName = d.AccountName}))
{
  nvc.Add(x.ItemName, x.AccountName);
}

Upvotes: 4

devdigital
devdigital

Reputation: 34349

Is there any reason you're not using a Dictionary<string, string> as your names/keys appear to be unique? This would be faster and you could use the ToDictionary standard query operator.

Also, if you did wish to use an extension method (although as Servy says a for loop is the right solution here), then you could write your own - see here.

Upvotes: 3

Rohit Vats
Rohit Vats

Reputation: 81253

Try this -

donations.ForEach(x =>
         {
             int index = donations.IndexOf(x);
             nvc.Add("item_name_" + index, x.AccountName);
         });

Upvotes: -1

Servy
Servy

Reputation: 203834

LINQ doesn't have a ForEach method, and for good reason. LINQ is for performing queries. It is designed to get information from some data source. It is not designed to mutate data sources. LINQ queries shouldn't cause side effects, which is exactly what you're doing here.

The List class does have a ForEach method, which is what you are using. Because it's not actually in the System.Linq namespace it's not technically a part of LINQ.

There is nothing wrong with the for loop in your question. It would be wrong (from a good practice perspective) to try to change it in the way that you're trying to.

Here is a link that discusses the matter in more detail.

Now, if you want to ignore that advice and use a ForEach method anyway, it's not hard to write one that provides an index to the action:

public static void ForEach<T>(this IEnumerable<T> sequence, Action<int, T> action)
{
    // argument null checking omitted
    int i = 0;
    foreach (T item in sequence)
    {
        action(i, item);
        i++;
    }
}

Upvotes: 40

Related Questions