var q = from t in dict
from v in t.Value.elements
select new { name = t.Key, element = v };
Il metodo qui è Enumerable.SelectMany. Utilizzando la sintassi metodo di estensione:
var q = dict.SelectMany(t => t.Value.elements.Select(v => new { name = t.Key, element = v }));
EDIT
noti che si potrebbe anche usare t.Value.name
sopra, invece di t.Key
, dal momento che questi valori sono uguali.
Quindi, cosa sta succedendo qui?
La sintassi di comprensione delle query è probabilmente la più semplice da comprendere; puoi scrivere un blocco iteratore equivalente per vedere cosa sta succedendo. Non possiamo farlo semplicemente con un tipo anonimo, però, quindi dovremo dichiarare un tipo di tornare:
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 };
}
Come circa il metodo di sintassi di estensione (o, ciò che è veramente succedendo qui)?
(Questo è ispirato in parte dal post di Eric Lippert a https://stackoverflow.com/a/2704795/385844, ho avuto una spiegazione molto più complicato, poi ho letto che, e si avvicinò con questo :)
Diciamo che vogliamo evitare dichiarando la NameElement genere. Potremmo usare un tipo anonimo passando una funzione.Ci piacerebbe cambiare la chiamata da questo:
var q = GetResults(dict);
a questo:
var q = GetResults(dict, (string1, string2) => new { name = string1, element = string2 });
L'espressione lambda (string1, string2) => new { name = string1, element = string2 }
rappresenta una funzione che prende 2 stringhe - definito dalla lista degli argomenti (string1, string2)
- e restituisce un istanza del tipo anonimo inizializzato con quelle stringhe - definito dall'espressione new { name = string1, element = string2 }
.
L'attuazione corrispondente è questo:
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);
}
Tipo inferenza ci permette di richiamare questa funzione senza specificare T
per nome. Questo è utile, perché (per quanto ne sappiamo come programmatori C#), il tipo che stiamo usando non ha un nome: è anonimo.
nota che la variabile t
è ora pair
, per evitare confusione con il parametro di tipo T
, e v
è ora e
, per "elemento". Abbiamo anche cambiato il tipo del primo parametro in uno dei suoi tipi di base, IEnumerable<KeyValuePair<string, MyStruct>>
. È più prolisso, ma rende il metodo più utile e alla fine sarà utile. Poiché il tipo non è più un tipo di dizionario, abbiamo anche modificato il nome del parametro da dict
a pairs
.
Potremmo generalizzare ulteriormente. Il secondo foreach
ha l'effetto di proiettare una coppia chiave-valore in una sequenza di tipo T. L'intero effetto potrebbe essere incapsulato in una singola funzione; il tipo delegato sarà Func<KeyValuePair<string, MyStruct>, T>
. Il primo passo è quello di refactoring il metodo quindi abbiamo una singola istruzione che converte l'elemento pair
in una sequenza, utilizzando il metodo Select
per richiamare il resultSelector
delegato:
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;
}
Ora si può facilmente cambiare 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;
}
Il sito di chiamata ora si presenta così; notare come l'espressione lambda ora incorpora la logica che abbiamo rimosso dal corpo del metodo, quando abbiamo cambiato la sua firma:
var q = GetResults(dict, pair => pair.Value.elements.Select(e => new { name = pair.Key, element = e }));
Per rendere il metodo più utile (e la sua attuazione meno prolissa), cerchiamo di sostituire il tipo KeyValuePair<string, MyStruct>
con un digitare parametro, TSource
. Cambieremo alcuni altri nomi al tempo stesso:
T -> TResult
pairs -> sourceSequence
pair -> sourceElement
E, solo per calci, ce la faremo un metodo di estensione:
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;
}
E il gioco è fatto: SelectMany! Bene, la funzione ha ancora il nome sbagliato e l'implementazione effettiva include la convalida che la sequenza sorgente e la funzione selector non siano nulle, ma questa è la logica principale.
Da MSDN: SelectMany
"proietta ogni elemento di una sequenza su un oggetto IEnumerable e appiattisce le sequenze risultanti in una sequenza."
@sinanakyazici la domanda non specifica che l'output deve essere memorizzato in un dizionario (e, infatti, come si nota correttamente, fuori c annot be). – phoog
@phoog hai ragione. Mi riferisco male. Così ho cancellato il mio commento. – sinanakyazici
@sinanakyazici per qualche motivo non posso cancellare (né modificare) il mio commento sul browser del mio telefono :( – phoog