To support grouping, the System.Linq namespace provides an interface named IGrouping. The IGrouping interface starts as follows: public interface IGrouping<TKey, TElement> : IEnumerable<TElement>, IEnumerable The IGrouping interface implements both the generic IEnumerable<> and the non-generic IEnumerable interfaces. This means that the IGrouping interface gives you access to the members of the Enumerable class.
When you create a grouping of values, the resulting list is stored in a variable of type IGrouping. As a result, when you create and execute a group, the compiler translates it into an IGrouping expression. To allow you to create a group, the Enumerable class is equipped with a method named GroupBy that is overloaded with 8 versions. The simplest version uses the following syntax: public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector); This method takes two arguments but only one is required. In our introduction to grouping, we saw that its operation identifies the categories of items from the from variable. Each category is referred to as a key and each category can be recognized as a TKey object of the IGrouping list. This allows you to access each category. In fact, you can access a category and perform an operation on it. Although the IGrouping interface inherits most of its functionality from the IEnumerable interface and implemented through the Enumerable class, it is equipped with only one property, named Key. To get the value of an IGrouping category, you can retrieve it from the Key property. Here is an example: using System; using System.Linq; using System.Drawing; using System.Windows.Forms; using System.Collections.Generic; public class VideoCollection : Form { private ListBox lbxVideos; public VideoCollection() { InitializeComponent(); } void InitializeComponent() { lbxVideos = new ListBox(); lbxVideos.Width = 185; lbxVideos.Location = new System.Drawing.Point(12, 12); Controls.Add(lbxVideos); Text = "Video Collection"; Controls.Add(lbxVideos); Size = new System.Drawing.Size(215, 145); Load += new EventHandler(VideoCollectionLoad); } private void VideoCollectionLoad(object sender, EventArgs e) { Video[] lstVideos = new Video[] { new Video("Platoon", "R"), new Video("Two for the Money", "R"), new Video("Cousin Vinny (My)", "R"), new Video("Natural Born Killers", "R"), new Video("Her Alibi", "PG-13"), new Video("Distinguished Gentleman (The)", "R"), new Video("Wall Street", "R"), new Video("Memoirs of a Geisha", "PG-13") }; var vdos = lstVideos.GroupBy(rate => rate.Rating); foreach (var vdo in vdos) lbxVideos.Items.Add(vdo.Key); } [STAThread] public static int Main() { System.Windows.Forms.Application.Run(new VideoCollection()); return 0; } } public class Video { public string Title { get; set; } public string Rating { get; set; } public Video(string title, string rating) { Title = title; Rating = rating; } } This would produce:
To access all members of the class, nest one foreach loop in another. Use the nested loop to access each class member. Here is an example: var vdos = lstVideos.GroupBy(vdo => vdo.Rating); foreach (var vdo in vdos) { foreach (var present in vdo) { ListViewItem lviVideo = new ListViewItem(present.ShelfNumber.ToString()); lviVideo.SubItems.Add(present.Title); lviVideo.SubItems.Add(present.Director); lviVideo.SubItems.Add(present.Rating); lviVideo.SubItems.Add(present.YearReleased.ToString()); lviVideo.SubItems.Add(present.WideScreen.ToString()); lvwCollection.Items.Add(lviVideo); } }
The other argument of the Enumerable.GroupBy() method specifies the records to display from the class member, for each key category (that class member holds the values that will be presented for each category). Here is an example: using System;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
using System.Collections.Generic;
public class VideoCollection : Form
{
private ColumnHeader colTitle;
private ColumnHeader colRating;
ListView lvwCollection;
public VideoCollection()
{
InitializeComponent();
}
void InitializeComponent()
{
lvwCollection = new ListView();
lvwCollection.Anchor = AnchorStyles.Left | AnchorStyles.Top |
AnchorStyles.Right | AnchorStyles.Bottom;
lvwCollection.FullRowSelect = true;
lvwCollection.GridLines = true;
lvwCollection.Location = new Point(12, 12);
lvwCollection.Size = new System.Drawing.Size(235, 137);
lvwCollection.View = View.Details;
colRating = new ColumnHeader();
colRating.Text = "Rating";
colRating.Width = 50;
colRating.TextAlign = HorizontalAlignment.Center;
lvwCollection.Columns.Add(colRating);
colTitle = new ColumnHeader();
colTitle.Text = "Video Title";
colTitle.Width = 180;
lvwCollection.Columns.Add(colTitle);
Text = "Video Collection";
MaximizeBox = false;
Size = new System.Drawing.Size(265, 188);
Controls.Add(lvwCollection);
Load += new EventHandler(VideoCollectionLoad);
}
private void VideoCollectionLoad(object sender, EventArgs e)
{
Video[] lstVideos = new Video[]
{
new Video(274880, "Platoon", "Oliver Stone", "R", 1986, false),
new Video(792075, "Two for the Money", "D.J. Caruso", "R", 2008, true),
new Video(283748, "Cousin Vinny (My)", "Jonathan Lynn", "R", 1992, false),
new Video(593940, "Natural Born Killers", "Oliver Stone", "R", 1994, true),
new Video(900245, "Her Alibi", "Bruce Beresford", "PG-13", 1998, false),
new Video(773022, "Distinguished Gentleman (The)", "Jonathan Lynn", "R", 1992, false),
new Video(961973, "Wall Street", "Oliver Stone", "R", 2000, false),
new Video(180358, "Memoirs of a Geisha", "Rob Marshall", "PG-13", 2006, true)
};
var vdos = lstVideos.GroupBy(rate => rate.Rating, vdo => vdo.Title);
foreach (var vdo in vdos)
{
foreach (var present in vdo)
{
// Create each category based on the "Key"
ListViewItem lviCollection = new ListViewItem(vdo.Key);
// Select the title(s) in that "Key" or category
lviCollection.SubItems.Add(present.ToString());
lvwCollection.Items.Add(lviCollection);
}
}
}
[STAThread]
public static int Main()
{
System.Windows.Forms.Application.Run(new VideoCollection());
return 0;
}
}
public class Video
{
public int ShelfNumber { get; set; }
public string Title { get; set; }
public string Director { get; set; }
public string Rating { get; set; }
public int YearReleased { get; set; }
public bool WideScreen { get; set; }
public Video(int id = 0,
string title = "",
string dir = "",
string rating = "",
int year = 0,
bool hd = false)
{
ShelfNumber = id;
Title = title;
Director = dir;
Rating = rating;
YearReleased = year;
WideScreen = hd;
}
}
This would produce:
When you create a grouping, you get a list of categories of values and that list becomes ready to be used. In some cases, before exploring the list, you may want to perform an operation on it. One way you can do this, you can store that list in a (local) variable and use that variable as if it were a from variable. To declare a variable to store the grouping values, you use the into contextual keyword through the following formula: var SubListName = from ValueHolder in List group ValueHolder by Category into GroupVariable ...; The GroupVariable is the new factor in our formula. You specify it as a regular name of a variable. Here is an example: var empls = from staffMembers in employees group staffMembers by staffMembers.Gender into Categories After creating the name, you can perform any operation on it inside the LINQ statement. The variable is of type IGrouping. This means that you can access its Key property or you can access one of the methods that the interface gets from IEnumerable, and then use it as you see fit. Here is an example: var empls = from staffMembers in employees group staffMembers by staffMembers.Gender into Categories where Categories.Contains(students[0]) Before ending the LINQ statement, you must create either a group...by expression or a select statement that uses the into variable. Here is an example: var videos = from vdos in lstVideos group vdos by vdos.Rating into categories where categories.Contains(lstVideos[0]) select categories; This statement, particularly the Enumerable.Contains(lstVideos[0]) produces only the category (group) identified as the first index (0) of the values in the main list (students):
Notice that all records in the final result have a common category, which in this case is the R rating of each video. For this reason, you can omit that column when presenting the values to the user. Here is an example (the column for the rating was removed from the list view): var videos = from vdos in lstVideos group vdos by vdos.Rating into categories where categories.Contains(lstVideos[0]) select categories; foreach (var toView in videos) { foreach (Video item in toView) { ListViewItem lviStudent = new ListViewItem(item.ShelfNumber); lviStudent.SubItems.Add(item.Title); lviStudent.SubItems.Add(item.Director); lviStudent.SubItems.Add(item.Length); // lviStudent.SubItems.Add(item.Rating); lviStudent.SubItems.Add(item.YearReleased); lvwVideos.Items.Add(lviStudent); } } This would produce:
In the same way, to get the category stored in the second index of the grouping, you would use Enumerable.Contains(lstVideos[1]). Of course, this means that you can use grouping and the into operator to get a list of items of only one particular category. Although the GroupVariable can be selected or grouped...by, it cannot be used outside the LINQ statement. It is only available in the local LINQ expression. |
|
|||||||||
|