21

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!

James Hill
  • 56,052
  • 18
  • 138
  • 155

9 Answers9

26

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++;
    }
}
Servy
  • 193,745
  • 23
  • 295
  • 406
15

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);
Tiago Raposo
  • 159
  • 1
  • 2
11

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).

lc.
  • 105,606
  • 20
  • 147
  • 176
6

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;
});
Patrick Ribbing
  • 111
  • 1
  • 5
3

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.

Community
  • 1
  • 1
devdigital
  • 33,472
  • 8
  • 93
  • 117
3

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);
}
wageoghe
  • 26,334
  • 13
  • 79
  • 113
1

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.

Soroush Falahati
  • 1,914
  • 1
  • 25
  • 34
0

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));
-1

Try this -

donations.ForEach(x =>
         {
             int index = donations.IndexOf(x);
             nvc.Add("item_name_" + index, x.AccountName);
         });
Rohit Vats
  • 74,365
  • 12
  • 144
  • 173
  • 6
    You're better off enumerating an intermediate collection with indices (a la @lc's answer) than searching the list on every iteration (`.IndexOf`) – Austin Salonen Oct 24 '12 at 17:35
  • 3
    What would happen if an item was in the list multiple times? (Note that in the OP's code all of the items are identical.) It's not *just* about the terrible performance after all; it doesn't even *work*. – Servy Oct 24 '12 at 17:37
  • So, is there any another way of finding index w/o creating an intermediate collection..? – Rohit Vats Oct 24 '12 at 17:38
  • 2
    @Sevy - Oops..!! Yeah it makes sense now to me. Just completely forget the uniqueness part. Downvotes accepted with honour now. :) – Rohit Vats Oct 24 '12 at 17:40
  • @RV1987 Sure. Use a `for` loop like is done in the OP's code. Or you could make a `ForEach` method that provides an index, like my answer shows, or you could create an integer `count` and increment it yourself in the loop. – Servy Oct 24 '12 at 17:40
  • 1
    @Sevy - Yeah thanks Servy got the point now..That's why i said atleast tell me where i am wrong. – Rohit Vats Oct 24 '12 at 17:41