Problem
this.Items
is a list of products. Each product has a category (product.Attributes["category"]
).
I want to render a drop down list, where all the products are grouped by category.
This works well, but how could I improve it? Could there be a way to not use a dictionary?
protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
{
var groups = new Dictionary<string, List<ListItem>>();
// sort by category
foreach (ListItem product in this.Items)
{
if (!groups.ContainsKey(category))
{
groups.Add(category, new List<ListItem>());
}
groups[category].Add(product);
}
// render each category
foreach (var group in groups)
{
// <optgroup>
RenderOptionGroupBeginTag(group.Key, writer);
// each option
group.Value.ForEach(p => this.RenderListItem(p, writer));
// </optgroup>
RenderOptionGroupEndTag(writer);
}
}
Solution
Since ListItemCollection
implements IEnumerable
, but not IEnumerable<ListItem>
, you have to use Cast
before you can use other LINQ methods.
After you do that, you can use GroupBy
, as Steve Michael suggested and then work with the result almost exactly like with your groups
:
var categoryGroups = this.Items.Cast<ListItem>()
.GroupBy(i => i.Attributes["category"]);
foreach (var categoryGroup in categoryGroups)
{
RenderOptionGroupBeginTag(group.Key, writer);
foreach (var item in group)
{
this.RenderListItem(item, writer);
}
RenderOptionGroupEndTag(writer);
}
Some other changes I made to the code:
- I used
foreach
instead ofForEach()
.ForEach()
looks like a LINQ method, but it behaves very differently, since it doesn’t do any querying. I think that in cases like this,foreach
in the most suitable option. - Changing
groups
(andgroup
) tocategoryGroups
means that the code is clearer and so you can get rid of the loop comment. - I also removed the other comments, because I think the code is clear about what it does. If you want to emphasize that
RenderOptionGroupBeginTag
creates<optgroup>
, maybe you should rename it? Probably to something likeRenderOptgroupBeginTag
.
You could do something like this:
var groupedList = this.Items.GroupBy(i => i.Attributes["category"])
.Select(g =>new { Name = g.Key, Items = g.ToList()})
.ToList();
Is that along the lines of what you’re looking for?