Как заставить мои лямбда-выражения оценивать рано?Исправить лямбда-выражение странности? - PullRequest
10 голосов
/ 22 июля 2011

Я написал следующий код C #:

_locationsByRegion = new Dictionary<string, IEnumerable<string>>();
foreach (string regionId in regionIds)
{
    IEnumerable<string> locationIds = Locations
        .Where(location => location.regionId.ToUpper() == regionId.ToUpper())
        .Select(location => location.LocationId); //If I cast to an array here, it works.
    _locationsByRegion.Add(regionId, LocationIdsIds);
}

Этот код предназначен для создания словаря с моими "идентификаторами регионов" в качестве ключей и списками "идентификаторов местоположений" в качестве значений.

Однако в действительности получается, что я получаю словарь с «идентификаторами регионов» в качестве ключей, но значение для каждого ключа идентично: это список местоположений для последнего идентификатора региона в regionIds!

Похоже, это результат оценки лямбда-выражений.Я могу получить правильный результат, приведя список идентификаторов местоположений к массиву, но это похоже на кучу.

Что такое хорошая практика для обработки этой ситуации?

Ответы [ 4 ]

11 голосов
/ 22 июля 2011

Вы используете LINQ. Вы должны выполнить энергичную операцию, чтобы заставить его выполнить .Select. ToList () - хороший оператор для этого. Список является общим, его можно назначить IEnumberable напрямую.

В случае, если вы используете LINQ, он по умолчанию выполняет отложенную оценку. Операции ToList / eager заставляют произойти выбор. Перед использованием одного из этих операторов действие не выполняется. Это похоже на выполнение SQL в ADO.NET. Если у вас есть утверждение «Выбрать * из пользователей», которое фактически не выполняет запрос, пока вы не сделаете лишние вещи. Список ToList выполняет выбор.

8 голосов
/ 22 июля 2011

Ваше закрытие по переменной, а не по значению.

Создайте локальную копию переменной, чтобы вместо нее записать текущее значение из цикла foreach:

_locationsByRegion = new Dictionary<string, IEnumerable<string>>();
foreach (string regionId in regionIds)
{
    var regionToUpper = regionId.ToUpper();
    IEnumerable<string> locationIds = Locations
        .Where(location => location.regionId.ToUpper() == regionToUpper)
        .Select(location => location.LocationId); //If I cast to an array here, it works.
    _locationsByRegion.Add(regionId, LocationIdsIds);
}

Тогда прочитайте это:

http://msdn.microsoft.com/en-us/vcsharp/hh264182

edit - принудительное выполнение оценки также сработало бы, как предлагали другие, но в большинстве случаев оценка с нетерпением оказывается намного медленнее.

4 голосов
/ 22 июля 2011

Позвоните ToList() или ToArray() после Select(...). Таким образом, вся коллекция будет оценена прямо здесь.

0 голосов
/ 14 октября 2012

На самом деле вопрос заключается в создании поиска, что может быть достигнуто проще с помощью стандартного объединения групп LINQ:

var query = from regionId in regionIds
            join location in Locations
            on regionId.ToLower() equals location.regionId.ToLower() into g
            select new { RegionID = regionId, 
                         Locations = g.Select(location => location.LocationId) };

В этом случае все местоположения будут загружаться одновременно и группироваться в памяти.Также этот запрос не будет выполнен, пока вы не попытаетесь получить доступ к результатам или пока вы не преобразуете его в словарь:

var locationsByRegion = query.ToDictionary(x => x.RegionID, x => x.Locations);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...