Поделиться через


Практическое руководство. Различные способы группирования результатов (руководство по программированию на C#)

Обновлен: Ноябрь 2007

Группирование — одна из самых эффективных функций LINQ. В следующих примерах демонстрируются различные способы группирования данных:

  • По отдельному свойству.

  • По первой букве строкового свойства.

  • По расчетному числовому диапазону.

  • По логическому предикату или другому выражению.

  • По составному ключу.

Кроме того, два последних запроса передают свои результаты в новый анонимный тип, содержащий только имя и фамилию студента. Дополнительные сведения см. в разделе Предложение group (Справочник по C#).

Пример

Во всех примерах в данном разделе используются следующие вспомогательные классы и источники данных.

public class StudentClass
{
    #region data
    protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear };
    protected class Student
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int ID { get; set; }
        public GradeLevel Year;
        public List<int> ExamScores;
    }

    protected static List<Student> students = new List<Student>
    {
        new Student {FirstName = "Terry", LastName = "Adams", ID = 120, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 99, 82, 81, 79}},
        new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116, Year = GradeLevel.ThirdYear,ExamScores = new List<int>{ 99, 86, 90, 94}},
        new Student {FirstName = "Hanying", LastName = "Feng", ID = 117, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 93, 92, 80, 87}},
        new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114, Year = GradeLevel.FourthYear,ExamScores = new List<int>{ 97, 89, 85, 82}},
        new Student {FirstName = "Debra", LastName = "Garcia", ID = 115, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 35, 72, 91, 70}},
        new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 92, 90, 83, 78}},
        new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 88, 94, 65, 91}},
        new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 75, 84, 91, 39}},
        new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 97, 92, 81, 60}},
        new Student {FirstName = "Lance", LastName = "Tucker", ID = 119, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 68, 79, 88, 92}},
        new Student {FirstName = "Michael", LastName = "Tucker", ID = 122, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 94, 92, 91, 91}},
        new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 96, 85, 91, 60}}
    };
    #endregion

    //Helper method
    protected static int GetPercentile(Student s)
    {
        double avg = s.ExamScores.Average();
        return avg > 0 ? (int)avg / 10 : 0;
    }



    public void QueryHighScores(int exam, int score)
    {
        var highScores = from student in students
                         where student.ExamScores[exam] > score
                         select new {Name = student.FirstName, Score = student.ExamScores[exam]};

        foreach (var item in highScores)
        {
            Console.WriteLine("{0,-15}{1}", item.Name, item.Score);
        }
    }
}

public class Program
{
    public static void Main()
    {
        StudentClass sc = new StudentClass();
        sc.QueryHighScores(1, 90);

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

В следующем примере показывается группировка элементов источника с помощью использования отдельного свойства элемента в качестве ключа группы. В этом случае ключом является string. Для ключа можно также использовать подстроку. При операции группирования используется компаратор проверки на равенство, используемый по умолчанию для данного типа.

private static void GroupBySingleProperty()
{
    Console.WriteLine("Group by a single property in an object");

    // queryLastNames is an IEnumerable<IGrouping<string, DataClass.Student>>
    // var is easier to type.
    var queryLastNames =
        from student in students
        group student by student.LastName into newGroup
        orderby newGroup.Key
        select newGroup;

    foreach (var nameGroup in queryLastNames)
    {
        Console.WriteLine("Key: {0}", nameGroup.Key);
        foreach (var student in nameGroup)
        {
            Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
        }
    }
}
/* Output:
  Group by a single property in an object
  Key: Feng
          Feng, Hanying
  Key: Garcia
          Garcia, Hugo
          Garcia, Cesar
          Garcia, Debra
  Key: Mortensen
          Mortensen, Sven
  Key: O'Donnell
          O'Donnell, Claire
  Key: Omelchenko
          Omelchenko, Svetlana
  Key: Tucker
          Tucker, Michael
          Tucker, Lance
 */  

В следующем примере показывается группировка элементов источника, когда в качестве ключа группы используется не свойство объекта.

private static void GroupBySubstring()
{            
    Console.WriteLine("\r\nGroup by something other than a property of the object:");

    var queryFirstLetters =
        from student in students
        group student by student.LastName[0];

    foreach (var studentGroup in queryFirstLetters)
    {
        Console.WriteLine("Key: {0}", studentGroup.Key);
        // Nested foreach is required to access group items
        foreach (var student in studentGroup)
        {
            Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
        }
    }           
}
/* Output:
        Group by first character:
        Key: O
                Omelchenko, Svetlana
                O'Donnell, Claire
        Key: G
                Garcia, Hugo
                Garcia, Cesar
                Garcia, Debra
        Key: M
                Mortensen, Sven
        Key: T
                Tucker, Michael
                Tucker, Lance
        Key: F
                Feng, Hanying
     */

В следующем примере показывается группировка элементов источника с помощью использования числового диапазона в качестве ключа группы. Затем запрос передает результаты в анонимный тип, содержащий только имя и фамилию, а также диапазон процентилей, к которому принадлежит студент. Здесь используется анонимный тип, так как для отображения результатов не обязательно использовать весь объект Student. GetPercentile — это вспомогательная функция, которая рассчитывает процентиль на основании среднего балла студента:

static int GetPercentile(Student s)
{
   double avg = s.Scores.Average();
   return avg > 0 ? (int)avg / 10 : 0;
}
private static void GroupByRange()
{            
    Console.WriteLine("\r\nGroup by numeric range and project into a new anonymous type:");

    var queryNumericRange =
        from student in students
        let percentile = GetPercentile(student)
        group new { student.FirstName, student.LastName } by percentile into percentGroup
        orderby percentGroup.Key
        select percentGroup;

    // Nested foreach required to iterate over groups and group items.
    foreach (var studentGroup in queryNumericRange)
    {
        Console.WriteLine("Key: {0}", (studentGroup.Key * 10));
        foreach (var item in studentGroup)
        {
            Console.WriteLine("\t{0}, {1}", item.LastName, item.FirstName);
        }
    }            
}
/* Output:
     Group by numeric range and project into a new anonymous type:
     Key: 60
             Garcia, Debra
     Key: 70
             Omelchenko, Svetlana
             O'Donnell, Claire
     Key: 80
             Garcia, Hugo
             Mortensen, Sven
             Garcia, Cesar
             Feng, Hanying
             Tucker, Lance
     Key: 90
             Tucker, Michael
     */

В следующем примере показывается группировка элементов источника с помощью выражения логического сравнения. Как и в предыдущих примерах, результаты передаются в анонимный тип, поскольку весь элемент источника не требуется. Обратите внимание на то, что свойства в анонимном типе становятся свойствами в члене Key, а при выполнении запроса доступ к ним можно получить по имени.

private static void GroupByBoolean()
{            
    Console.WriteLine("\r\nGroup by a boolean into two groups with string keys");
    Console.WriteLine("\"True\" and \"False\" and project into a new anonymous type:");
    var queryGroupByAverages = from student in students
                               group new { student.FirstName, student.LastName }
                                    by student.ExamScores.Average() > 75 into studentGroup
                               select studentGroup;

    foreach (var studentGroup in queryGroupByAverages)
    {
        Console.WriteLine("Key: {0}", studentGroup.Key);
        foreach (var student in studentGroup)
            Console.WriteLine("\t{0} {1}", student.FirstName, student.LastName);
    }            
}
/* Output:
         Group by a boolean into two groups with string keys
         "True" and "False" and project into a new anonymous type:
         Key: True
                 Svetlana Omelchenko
                 Hugo Garcia
                 Sven Mortensen
                 Michael Tucker
                 Cesar Garcia
                 Hanying Feng
                 Lance Tucker
         Key: False
                 Claire O'Donnell
                 Debra Garcia
*/

В следующем примере показано, как использовать анонимный тип для инкапсуляции ключа, содержащего несколько значений. В этом случае второе значение ключа имеет тип Boolean и указывает, набрал ли студент более 85 баллов на первом экзамене. Группы можно сортировать по любому из свойств в данном ключе.

private static void GroupByCompositeKey()
{

    var queryHighScoreGroups =
        from student in students
        group student by new { FirstLetter = student.LastName[0], Score = student.ExamScores[0] > 85 } into studentGroup
        orderby studentGroup.Key.FirstLetter
        select studentGroup;

    Console.WriteLine("\r\nGroup and order by a compound key:");
    foreach (var scoreGroup in queryHighScoreGroups)
    {
        string s = scoreGroup.Key.Score == true ? "more than" : "less than";
        Console.WriteLine("Name starts with {0} who scored {1} 85", scoreGroup.Key.FirstLetter, s);
        foreach (var item in scoreGroup)
        {
            Console.WriteLine("\t{0} {1}", item.FirstName, item.LastName);
        }
    }
}
/* Output:
          Group and order by a compound key:
          Name starts with F who scored more than 85
                  Hanying Feng
          Name starts with G who scored more than 85
                  Hugo Garcia
                  Cesar Garcia
          Name starts with G who scored less than 85
                  Debra Garcia
          Name starts with M who scored more than 85
                  Sven Mortensen
          Name starts with O who scored more than 85
                  Svetlana Omelchenko
          Name starts with O who scored less than 85
                  Claire O'Donnell
          Name starts with T who scored more than 85
                  Michael Tucker
          Name starts with T who scored less than 85
                  Lance Tucker
       */

Компиляция кода

В этом примере содержатся ссылки на объекты, определенные в образце приложения в разделе Практическое руководство. Запрос коллекции объектов (руководство по программированию на C#). Чтобы скомпилировать и запустить этот метод, вставьте его в класс StudentClass в этом приложении и добавьте его вызов из метода Main.

Настраивая данный метод для своего приложения, помните, что для LINQ требуется .NET Framework версии 3.5, и проект должен содержать ссылку на библиотеку System.Core.dll и директиву Using для библиотеки System.Linq. Типы LINQ to SQL, LINQ to XML и LINQ to DataSet требуют дополнительных директив Using и ссылок. Дополнительные сведения см. в разделе Практическое руководство. Создание проекта LINQ.

См. также

Задачи

Практическое руководство. Вложенный запрос в операции группирования (Руководство по программированию на C#)

Практическое руководство. Группирование группы (руководство по программированию в C#)

Основные понятия

Выражения запросов LINQ (Руководство по программированию в C#)

Группировка данных

Ссылки

Предложение group (Справочник по C#)

Анонимные типы (Руководство по программированию в C#)

GroupBy

IGrouping<TKey, TElement>