tags:

views:

302

answers:

4

I asked a question in which one of the response contained the following LINQ code:

var selected = lstAvailableColors.Cast<ListItem>().Where(i => i.Selected).ToList();
selected.ForEach( x => { lstSelectedColors.Items.Add(x); });
selected.ForEach( x => { lstAvailableColors.Items.Remove(x);});

Can someone explain the above LINQ to a total newbie?

+2  A: 

It casts each item in the list to type ListItem, then selects only those whose Selected property is true. It then creates a new list containing just these items. For each item in the resulting list, it adds that item to the selected colors list and removes it from the available colors list.

tvanfosson
+3  A: 

Explanation from original question.

The LINQ version works in two parts. The first part is the first line which finds the currently selected items and stores the value in a List. It's very important that the line contain the .ToList() call because that forces the query to execute immediately vs. being delayed executed.

The next two lines iterate through each value which is selected and remove or add it to the appropriate list. Because the selected list is already stored we are no longer enumerating the collection when we modify it.

JaredPar
The 2nd and 3rd lines are pretty clear, but can you further explain the CAST in line 1. I guess I am trying to understand what order the linq query is executing?
Xaisoft
Basically: first, the code is casting all the elements in lstAvailableColors to type ListItem. Then, a filter is applied to generate a subset that represents all ListItems where Selected == true. Finally, the IEnumerable<ListItem> returned by the filter is converted into a List<ListItem>. -- Because of the enumerable semantics it's not *exactly* like this - but that's the general order you're looking for.
Erik Forbes
More accurately: It takes each element in the lstAvailableColors collection in turn. When it gets to each element, it casts it to a ListItem. Then, if the item's IsSelected is true, it adds it to a new list that it's created. Because of the way IEnumerable and iterators work, it works on one item at a time.
mquander
@mquander - true, but I didn't want to confuse matters by getting that explicit. =) +1 nonetheless for accuracy.
Erik Forbes
(not that +1 on a comment means a whole heck of a lot other than kudos =P)
Erik Forbes
+5  A: 

The LINQ operators use what's called a fluent interface, so you can read the first line as a series of function calls. Assuming that lstAvailableColors is IEnumerable<T>, the idea is that each available color flows through the LINQ operators.

Let's break it down:

var selected = lstAvailableColors
    // each item is cast to ListItem type
    .Cast<ListItem>() 
    // items that don't pass the test (Selected == true) are dropped
    .Where(i => i.Selected) 
    // turn the stream into a List<ListItem> object
    .ToList();

EDIT: As JaredPar pointed out, the last line above (ToList()) is very important. If you didn't do this, then each of the two selected.ForEach calls would re-run the query. This is called deferred execution and is an important part of LINQ.

You could rewrite this first line like this:

var selected = new List<ListItem>();

foreach (var item in lstAvailableColors)
{
    var listItem = (ListItem)item;

    if (!listItem.Selected)
         continue;

    selected.Add(listItem);
}

The last two lines are just another way to write a foreach loop and could be rewritten as:

foreach (var x in selected)
{
    lstSelectedColors.Items.Add(x);
}

foreach (var x in selected)
{
    lstAvailableColors.Items.Remove(X);
}

Probably the hardest part of learning LINQ is learning the flow of data and the syntax of lambda expressions.

Neil Williams
flow of data is exactly what is confusing.
Xaisoft
Nice explanation. So in the end of the first line, selected only contains items that are selected. Also, if selected is List<ListItem>, can I use that instead of var? What is the reason for using var?
Xaisoft
The only reason for using "var" is because sometimes you'll have things that are easy to understand but a pain to type, something awful like IEnumerable<KeyValuePair<int, ListItem>>. It does nothing but save keystrokes.
mquander
'var' is shorthand for asking the compiler to infer the variable type based on what the variable is assigned to. Yes, you can indeed replace it with List<ListItem>. It's really a matter of preference. Most people would use 'var' here because it's easy to see that it's a List<ListItem> because of the .Cast<ListItem> call and the .ToList() call.
Erik Forbes
ok, so there is no performance benefit to using var.
Xaisoft
Could I have used object or is that different from var?
Xaisoft
object would lose type safety. var is just a C# shorthand telling the compiler "figure it out for yourself" (the compiled code would look as if you typed out the List<ListItem>) object would be explicitly telling the compiler to treat the result as "object" and you wouldn't be able to call any methods on it other than the ones defined in object (ToString etc.) without casting back to List<ListItem>
Neil Williams
ok, got it. Thanks.
Xaisoft
+1  A: 

Maybe some translations would help

var selected = lstAvailableColors.Cast<ListItem>().Where(i => i.Selected).ToList();

could be written as:

List<ListItem> selected = new List<ListItem>();

foreach (ListItem item in lstAvailableColors)
{
    if (item.Selected)
        selected.Add(item);
}

Note that foreach implicitly casts the items on the list to whatever type the loop variable is, in this case ListItem, so that takes care of the Cast<ListItem> on the list. Where filters out any items for which the expression is false, so I do the same thing with an if statement. Finally, ToList turns the sequence into a list, so I just build up a list as I go. The end result is the same.

And:

selected.ForEach( x => { lstSelectedColors.Items.Add(x); });
selected.ForEach( x => { lstAvailableColors.Items.Remove(x); });

could be written as:

foreach (ListItem item in selected)
{
    lstSelectedColors.Items.Add(item);
    lstAvailableColors.Items.Remove(item);
}

I doubt if there's a good reason for writing it the more obscure way in that case.

Daniel Earwicker