var q = from t in dict
from v in t.Value.elements
select new { name = t.Key, element = v };
Метод здесь Enumerable.SelectMany.Использование синтаксиса метода расширения:
var q = dict.SelectMany(t => t.Value.elements.Select(v => new { name = t.Key, element = v }));
EDIT
Обратите внимание, что вы также можете использовать t.Value.name
выше вместо t.Key
, так как эти значения равны.
Итак, что здесь происходит?
Синтаксис понимания запросов, вероятно, проще всего понять;Вы можете написать эквивалентный блок итератора, чтобы увидеть, что происходит.Однако мы не можем сделать это просто с анонимным типом, поэтому мы объявим тип для возврата:
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 };
}
Как насчет синтаксиса метода расширения (или, что действительно )что происходит здесь??
(Это частично вдохновлено постом Эрика Липперта в https://stackoverflow.com/a/2704795/385844; У меня было гораздо более сложное объяснение, потом я его прочитал и придумал:)
Допустим, мы хотим избежать объявления типа NameElement.Мы могли бы использовать анонимный тип, передав функцию.Мы изменили бы вызов с этого:
var q = GetResults(dict);
на этот:
var q = GetResults(dict, (string1, string2) => new { name = string1, element = string2 });
Лямбда-выражение (string1, string2) => new { name = string1, element = string2 }
представляет функцию, которая принимает 2 строки - определенные списком аргументов(string1, string2)
- и возвращает экземпляр анонимного типа, инициализированный этими строками - определяется выражением new { name = string1, element = string2 }
.
Соответствующая реализация такова:
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);
}
Вывод типапозволяет нам вызывать эту функцию без указания T
по имени.Это удобно, потому что (насколько мы знаем, как программисты на C #), тип, который мы используем, не имеет имени: он анонимный.
Обратите внимание, что переменная t
теперь pair
, чтобы избежать путаницы с параметром типа T
, а v
теперь e
, для "элемента".Мы также изменили тип первого параметра на один из его базовых типов, IEnumerable<KeyValuePair<string, MyStruct>>
.Это более многословно, но делает метод более полезным и в конечном итоге будет полезным.Поскольку тип больше не является типом словаря, мы также изменили имя параметра с dict
на pairs
.
Мы могли бы обобщить это далее.Второй foreach
имеет эффект проецирования пары ключ-значение на последовательность типа T. Весь этот эффект может быть заключен в одну функцию;тип делегата будет Func<KeyValuePair<string, MyStruct>, T>
.Первым шагом является рефакторинг метода, поэтому у нас есть один оператор, который преобразует элемент pair
в последовательность, используя метод Select
для вызова делегата resultSelector
:
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;
}
Теперь мыможно легко изменить подпись:
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;
}
Сайт вызова теперь выглядит так;обратите внимание, что лямбда-выражение теперь включает логику, которую мы удалили из тела метода, когда мы изменили его сигнатуру:
var q = GetResults(dict, pair => pair.Value.elements.Select(e => new { name = pair.Key, element = e }));
Чтобы сделать метод более полезным (и его реализация менее многословной), давайте заменим тип KeyValuePair<string, MyStruct>
с параметром типа, TSource
.В то же время мы изменим некоторые другие имена:
T -> TResult
pairs -> sourceSequence
pair -> sourceElement
И, просто для удовольствия, мы сделаем это методом расширения:
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;
}
И вот оно у вас есть: Выберите много!Ну, у функции все еще есть неправильное имя, и фактическая реализация включает в себя проверку того, что исходная последовательность и функция селектора не равны NULL, но это основная логика.
С MSDN :SelectMany
"проецирует каждый элемент последовательности в IEnumerable и объединяет результирующие последовательности в одну последовательность."