group (Cláusula, Referencia de C#)
Actualización: noviembre 2007
La cláusula group devuelve una secuencia de objetos IGrouping<TKey, TElement> que contienen cero o más elementos que coinciden con el valor de clave del grupo. Por ejemplo, puede agrupar una secuencia de cadenas según la primera letra de cada cadena. En este caso, la primera letra es la clave, es de tipo char y se almacena en la propiedad Key de cada objeto IGrouping<TKey, TElement>. El compilador deduce el tipo de la clave.
Puede finalizar una expresión de consulta con una cláusula group, como se muestra en el ejemplo siguiente:
// Query variable is an IEnumerable<IGrouping<char, Student>>
var studentQuery1 =
from student in students
group student by student.Last[0];
Si desea realizar operaciones de consulta adicionales en cada grupo, puede especificar un identificador temporal mediante la palabra clave contextual into. Cuando se utiliza into, es necesario continuar con la consulta y finalmente terminarla con una instrucción select u otra cláusula group, como se muestra en el extracto siguiente:
// Group students by the first letter of their last name
// Query variable is an IEnumerable<IGrouping<char, Student>>
var studentQuery2 =
from student in students
group student by student.Last[0] into g
orderby g.Key
select g;
En la sección Ejemplo de este tema se proporcionan ejemplos más completos sobre el uso de group con y sin into.
Enumerar los resultados de una consulta Group
Dado que los objetos IGrouping<TKey, TElement> generados por una consulta group son esencialmente una lista de listas, debe utilizar un bucle foreach anidado para tener acceso a los elementos de cada grupo. El bucle exterior recorre en iteración las claves de grupo y el bucle interno recorre en iteración cada elemento del propio grupo. Un grupo puede tener una clave pero ningún elemento. A continuación se muestra el bucle foreach que ejecuta la consulta en los ejemplos de código anteriores:
// Iterate group items with a nested foreach. This IGrouping encapsulates
// a sequence of Student objects, and a Key of type char.
// For convenience, var can also be used in the foreach statement.
foreach (IGrouping<char, Student> studentGroup in studentQuery2)
{
Console.WriteLine(studentGroup.Key);
// Explicit type for student could also be used here.
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}", student.Last, student.First);
}
}
Tipos de clave
Las claves de grupo pueden ser de cualquier tipo, como una cadena, un tipo numérico integrado, un tipo con nombre definido por el usuario o un tipo anónimo.
Agrupar por cadena
En los ejemplos de código anteriores se utilizó el tipo char. En su lugar, podría haberse especificado una clave de cadena, por ejemplo, los apellidos completos:
// Same as previous example except we use the entire last name as a key.
// Query variable is an IEnumerable<IGrouping<string, Student>>
var studentQuery3 =
from student in students
group student by student.Last;
Agrupar por valor booleano
En el ejemplo siguiente se muestra el uso de un valor booleano para una clave con el fin de dividir los resultados en dos grupos. Observe que el valor se genera a partir de una subexpresión en la cláusula group.
class GroupSample1
{
// The element type of the data source.
public class Student
{
public string First { get; set; }
public string Last { get; set; }
public int ID { get; set; }
public List<int> Scores;
}
public static List<Student> GetStudents()
{
// Use a collection initializer to create the data source. Note that each element
// in the list contains an inner sequence of scores.
List<Student> students = new List<Student>
{
new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
};
return students;
}
static void Main()
{
// Obtain the data source.
List<Student> students = GetStudents();
// Group by true or false.
// Query variable is an IEnumerable<IGrouping<bool, Student>>
var booleanGroupQuery =
from student in students
group student by student.Scores.Average() >= 80; //pass or fail!
// Execute the query and access items in each group
foreach (var studentGroup in booleanGroupQuery)
{
Console.WriteLine(studentGroup.Key == true ? "High averages" : "Low averages");
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
}
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Low averages
Omelchenko, Svetlana:77.5
O'Donnell, Claire:72.25
Garcia, Cesar:75.5
High averages
Mortensen, Sven:93.5
Garcia, Debra:88.25
*/
Agrupar por intervalo numérico
En el ejemplo siguiente se utiliza una expresión para crear claves de grupo numéricas que representan un intervalo de percentil. Observe el uso de let como lugar adecuado para almacenar un resultado de una llamada a método, para no tener que llamar al método dos veces en la cláusula group. Observe también cómo en la cláusula group, para evitar una excepción por "división entre cero", el código comprueba que el estudiante no tenga ninguna media de cero. Para obtener más información sobre cómo utilizar métodos en expresiones de consulta de forma segura, vea Cómo: Controlar excepciones con expresiones de consultas (Guía de programación de C#).
class GroupSample2
{
// The element type of the data source.
public class Student
{
public string First { get; set; }
public string Last { get; set; }
public int ID { get; set; }
public List<int> Scores;
}
public static List<Student> GetStudents()
{
// Use a collection initializer to create the data source. Note that each element
// in the list contains an inner sequence of scores.
List<Student> students = new List<Student>
{
new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
};
return students;
}
// This method groups students into percentile ranges based on their
// grade average. The Average method returns a double, so to produce a whole
// number it is necessary to cast to int before dividing by 10.
static void Main()
{
// Obtain the data source.
List<Student> students = GetStudents();
// Write the query.
var studentQuery =
from student in students
let avg = (int)student.Scores.Average()
group student by (avg == 0 ? 0 : avg / 10) into g
orderby g.Key
select g;
// Execute the query.
foreach (var studentGroup in studentQuery)
{
int temp = studentGroup.Key * 10;
Console.WriteLine("Students with an average between {0} and {1}", temp, temp + 10);
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
}
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Students with an average between 70 and 80
Omelchenko, Svetlana:77.5
O'Donnell, Claire:72.25
Garcia, Cesar:75.5
Students with an average between 80 and 90
Garcia, Debra:88.25
Students with an average between 90 and 100
Mortensen, Sven:93.5
*/
Agrupar por claves compuestas
Utilice una clave compuesta cuando desee agrupar los elementos según más de una clave. Para crear una clave compuesta se usa un tipo anónimo o un tipo con nombre para contener el elemento de clave. En el ejemplo siguiente, supongamos que una clase Person se ha declarado con los miembros denominados surname y city. La cláusula group hace que se cree un grupo independiente para cada conjunto de personas con el mismo apellido y la misma ciudad.
group person by new {name = person.surname, city = person.city};
Utilice un tipo con nombre si debe pasar la variable de consulta a otro método. Para crear una clase especial, utilice propiedades autoimplementadas para las claves e invalide después los métodos Equals y GetHashCode. También puede utilizar una estructura, en cuyo caso no es estrictamente necesario invalidar esos métodos. Para obtener más información, consulte Cómo: Implementar una clase ligera con propiedades autoimplementadas (Guía de programación de C#) y Cómo: Buscar archivos duplicados en un árbol de directorios (LINQ). El último tema contiene un ejemplo de código que muestra cómo utilizar una clave compuesta con un tipo con nombre.
Ejemplo
En el ejemplo siguiente se muestra el patrón estándar para ordenar los datos de origen en grupos cuando no se aplica ninguna lógica de consulta adicional a los grupos. Esto se denomina agrupación sin continuación. Los elementos de una matriz de cadenas se agrupan por la primera letra. El resultado de la consulta es un tipo IGrouping<TKey, TElement> que contiene una propiedad Key pública de tipo char y una colección IEnumerable<T> que contiene cada elemento de la agrupación.
El resultado de una cláusula group es una secuencia de secuencias. Por consiguiente, para tener acceso a los elementos individuales de cada grupo devuelto, utilice un bucle foreach anidado dentro del bucle que recorre en iteración las claves de grupo, como se muestra en el ejemplo siguiente.
class GroupExample1
{
static void Main()
{
// Create a data source.
string[] words = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese" };
// Create the query.
var wordGroups =
from w in words
group w by w[0];
// Execute the query.
foreach (var wordGroup in wordGroups)
{
Console.WriteLine("Words that start with the letter '{0}':", wordGroup.Key);
foreach (var word in wordGroup)
{
Console.WriteLine(word);
}
}
// Keep the console window open in debug mode
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Words that start with the letter 'b':
blueberry
banana
Words that start with the letter 'c':
chimpanzee
cheese
Words that start with the letter 'a':
abacus
apple
*/
En este ejemplo se muestra cómo aplicar lógica adicional a los grupos después de haberlos creado, mediante el uso de una continuación con into. Para obtener más información, consulte into (Referencia de C#). En el ejemplo siguiente se consulta cada grupo para seleccionar sólo aquéllos cuyo valor de clave sea una vocal.
class GroupClauseExample2
{
static void Main()
{
// Create the data source.
string[] words2 = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese", "elephant", "umbrella", "anteater" };
// Create the query.
var wordGroups2 =
from w in words2
group w by w[0] into grps
where (grps.Key == 'a' || grps.Key == 'e' || grps.Key == 'i'
|| grps.Key == 'o' || grps.Key == 'u')
select grps;
// Execute the query.
foreach (var wordGroup in wordGroups2)
{
Console.WriteLine("Groups that start with a vowel: {0}", wordGroup.Key);
foreach (var word in wordGroup)
{
Console.WriteLine(" {0}", word);
}
}
// Keep the console window open in debug mode
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Groups that start with a vowel: a
abacus
apple
anteater
Groups that start with a vowel: e
elephant
Groups that start with a vowel: u
umbrella
*/
Comentarios
En tiempo de compilación, las cláusulas group se convierten en llamadas al método GroupBy.
Vea también
Tareas
Cómo: Agrupar un grupo (Guía de programación de C#)
Cómo: Agrupar resultados de distintas maneras (Guía de programación de C#)
Cómo: Realizar una subconsulta en una operación de agrupación (Guía de programación de C#)
Conceptos
Expresiones de consultas con LINQ (Guía de programación de C#)