var q = from t in dict
from v in t.Value.elements
select new { name = t.Key, element = v };
El método aquí es Enumerable.SelectMany. Utilizando la sintaxis de método de extensión:
var q = dict.SelectMany(t => t.Value.elements.Select(v => new { name = t.Key, element = v }));
EDITAR
Tenga en cuenta que también se puede utilizar t.Value.name
anterior, en lugar de t.Key
, ya que estos valores son iguales.
Entonces, ¿qué está pasando aquí?
La sintaxis de consulta-comprensión es probablemente la más fácil de entender; puede escribir un bloque de iterador equivalente para ver qué está sucediendo. No podemos hacer eso, simplemente con un tipo anónimo, sin embargo, lo que vamos a declarar un tipo para volver:
class NameElement
{
public string name { get; set; }
public string element { get; set; }
}
IEnumerable<NameElement> GetResults(Dictionary<string, MyStruct> dict)
{
foreach (KeyValuePair<string, MyStruct> t in dict)
foreach (string v in t.Value.elements)
yield return new NameElement { name = t.Key, element = v };
}
¿Qué hay de la sintaxis método de extensión (o, lo que es realmente pasando aquí)?
(Esto se inspiró en parte por el post de Eric Lippert en https://stackoverflow.com/a/2704795/385844, tenía una explicación mucho más complicado, entonces leí eso, y se acercó con esto :)
Digamos que queremos evitar que declara la NameElement tipo. Podríamos usar un tipo anónimo al pasar una función.Nos gustaría cambiar la llamada de este:
var q = GetResults(dict);
a esto:
var q = GetResults(dict, (string1, string2) => new { name = string1, element = string2 });
La expresión lambda (string1, string2) => new { name = string1, element = string2 }
representa una función que toma 2 cuerdas - definido por la lista de argumentos (string1, string2)
- y devuelve una instancia del tipo anónimo inicializado con esas cadenas, definido por la expresión new { name = string1, element = string2 }
.
La aplicación correspondiente es la siguiente:
IEnumerable<T> GetResults<T>(
IEnumerable<KeyValuePair<string, MyStruct>> pairs,
Func<string, string, T> resultSelector)
{
foreach (KeyValuePair<string, MyStruct> pair in pairs)
foreach (string e in pair.Value.elements)
yield return resultSelector.Invoke(t.Key, v);
}
La inferencia de tipos nos permite llamar a esta función sin especificar T
por su nombre. Eso es útil, porque (hasta donde sabemos, como programadores de C#), el tipo que estamos utilizando no tiene nombre: es anónimo.
Nota que la variable t
es ahora pair
, para evitar confusión con el parámetro de tipo T
, y v
es ahora e
, por "elemento". También hemos cambiado el tipo del primer parámetro a uno de sus tipos base, IEnumerable<KeyValuePair<string, MyStruct>>
. Es más prolijo, pero hace que el método sea más útil, y será útil al final. Como el tipo ya no es un tipo de diccionario, también hemos cambiado el nombre del parámetro de dict
a pairs
.
Podríamos generalizar esto más. El segundo foreach
tiene el efecto de proyectar un par de clave-valor a una secuencia de tipo T. Ese efecto completo podría ser encapsulado en una sola función; el tipo de delegado sería Func<KeyValuePair<string, MyStruct>, T>
. El primer paso es refactorizar el método por lo que tenemos una sola declaración que convierte el elemento pair
en una secuencia, utilizando el método de Select
para invocar el resultSelector
delegado:
IEnumerable<T> GetResults<T>(
IEnumerable<KeyValuePair<string, MyStruct>> pairs,
Func<string, string, T> resultSelector)
{
foreach (KeyValuePair<string, MyStruct> pair in pairs)
foreach (T result in pair.Value.elements.Select(e => resultSelector.Invoke(pair.Key, e))
yield return result;
}
Ahora podemos cambiar fácilmente la firma:
IEnumerable<T> GetResults<T>(
IEnumerable<KeyValuePair<string, MyStruct>> pairs,
Func<KeyValuePair<string, MyStruct>, IEnumerable<T>> resultSelector)
{
foreach (KeyValuePair<string, MyStruct> pair in pairs)
foreach (T result in resultSelector.Invoke(pair))
yield return result;
}
El sitio de la llamada ahora se ve así; observe cómo la expresión lambda ahora incorpora la lógica que hemos eliminado del cuerpo del método cuando cambiamos su firma:
var q = GetResults(dict, pair => pair.Value.elements.Select(e => new { name = pair.Key, element = e }));
Para que el método más útil (y su aplicación menos detallado), vamos a sustituir el tipo KeyValuePair<string, MyStruct>
con una tipo parámetro, TSource
. Vamos a cambiar algunos otros nombres al mismo tiempo:
T -> TResult
pairs -> sourceSequence
pair -> sourceElement
Y, sólo por diversión, vamos a hacer que sea un método de extensión:
static IEnumerable<TResult> GetResults<TSource, TResult>(
this IEnumerable<TSource> sourceSequence,
Func<TSource, IEnumerable<TResult>> resultSelector)
{
foreach (TSource sourceElement in sourceSequence)
foreach (T result in resultSelector.Invoke(pair))
yield return result;
}
Y ahí lo tienen: SelectMany! Bueno, la función todavía tiene un nombre incorrecto, y la implementación real incluye la validación de que la secuencia fuente y la función del selector no son nulas, pero esa es la lógica central.
De MSDN: SelectMany
"proyecta cada elemento de una secuencia a un IEnumerable y aplana las secuencias resultantes en una secuencia."
@sinanakyazici la pregunta no especifica que la salida debe almacenarse en un diccionario (y, de hecho, como se observa correctamente, salir c no sea). – phoog
@phoog tienes razón. Yo entiendo mal. Entonces borré mi comentario. – sinanakyazici
@sinanakyazici por alguna razón no puedo eliminar (ni editar) mi comentario en el navegador de mi teléfono :( – phoog