Подтвердить что ты не робот

Замена вложенного foreach на LINQ; изменять и обновлять свойство в пределах

Рассмотрим требование изменить элемент данных по одному или нескольким свойствам объекта с глубиной 5 или 6 уровней.

Существуют подкатегории, которые необходимо перебирать, чтобы получить свойство, которое нуждается в проверке и модификации.

Здесь мы вызываем метод, который очищает уличный адрес сотрудника. Поскольку мы меняем данные в циклах, для текущей реализации требуется цикл for для предотвращения исключения:

Невозможно назначить "someVariable", потому что это "переменная итерации foreach"

Здесь текущий алгоритм (obfuscated) с вложенными foreach и a for.

foreach (var emp in company.internalData.Emps)
{
    foreach (var addr in emp.privateData.Addresses)
    {
        int numberAddresses = addr.Items.Length;

        for (int i = 0; i < numberAddresses; i++)
        {
            //transform this street address via a static method
            if (addr.Items[i].Type =="StreetAddress")
               addr.Items[i].Text = CleanStreetAddressLine(addr.Items[i].Text);
        }
    }
}

Вопрос: Может ли этот алгоритм быть повторно реализован с помощью LINQ? Требование состоит в том, чтобы исходная коллекция изменила свои данные на этот вызов статического метода.

Обновление: Я думал/наклонялся в направлении решения типа jQuery/selector. Я специально не задавал этот вопрос таким образом. Я понимаю, что я преувеличиваю эту идею (никаких побочных эффектов). Спасибо всем! Если есть такой способ выполнения селектора типа jQuery, пожалуйста, посмотрите его!

4b9b3361

Ответ 1

LINQ не предназначен для изменения наборов объектов. Вы бы не ожидали, что инструкция SELECT sql изменит значения выбранных строк, не так ли? Это помогает вспомнить, что означает LINQ - Language Integrated Natural Запрос. Изменение объектов в запросе linq - это IMHO, анти-шаблон.

Ответ на Stan R. был бы лучшим решением с использованием цикла foreach, я думаю.

Ответ 2

foreach(var item in company.internalData.Emps
                        .SelectMany(emp => emp.privateData.Addresses)
                        .SelectMany(addr => addr.Items)
                        .Where(addr => addr.Type == "StreetAddress"))
     item.Text = CleanStreetAddressLine(item.Text);

Ответ 3

var dirtyAddresses = company.internalData.Emps.SelectMany( x => x.privateData.Addresses )
                                              .SelectMany(y => y.Items)
                                              .Where( z => z.Type == "StreetAddress");

  foreach(var addr in dirtyAddresses)
    addr.Text = CleanStreetAddressLine(addr.Text);

Ответ 4

Мне не нравится смешивать синтаксис "понимание запроса" и синтаксис точечного метода-запроса в том же самом выражении.

Мне нравится идея отделить запрос от действия. Они семантически различны, поэтому их разделение в коде часто имеет смысл.

var addrItemQuery = from emp in company.internalData.Emps
                    from addr in emp.privateData.Addresses
                    from addrItem in addr.Items
                    where addrItem.Type == "StreetAddress"
                    select addrItem;

foreach (var addrItem in addrItemQuery)
{
    addrItem.Text = CleanStreetAddressLine(addrItem.Text);
}

Несколько заметок стиля о вашем коде; они личны, поэтому я не могу согласиться:

  • В общем, я избегаю сокращений (Emps, emp, addr)
  • Непоследовательные имена более сбивают с толку (addr vs. Addresses): выберите один и придерживайтесь его
  • Слово "число" неоднозначно. Это может быть либо личность ( "Номер заключенного 378, пожалуйста, сделайте шаг вперед" ), либо счет ( "количество овец в этом поле равно 12." ). Поскольку мы используем обе концепции в коде много, полезно понять это. Я использую часто использовать "index" для первого и "count" для второго.
  • Наличие поля type - строка - это запах кода. Если вы можете сделать это enum, ваш код, вероятно, будет лучше.

Ответ 5

Грязный однострочный.

company.internalData.Emps.SelectMany(x => x.privateData.Addresses)
    .SelectMany(x => x.Items)
    .Where(x => x.Type == "StreetAddress")
    .Select(x => { x.Text = CleanStreetAddressLine(x.Text); return x; });

Ответ 6

LINQ не предоставляет возможность наличия побочных эффектов. однако вы могли бы сделать:

company.internalData.Emps.SelectMany(emp => emp.Addresses).SelectMany(addr => Addr.Items).ToList().ForEach(/*either make an anonymous method or refactor your side effect code out to a method on its own*/);

Ответ 7

Вы можете сделать это, но на самом деле вы этого не хотите. Несколько блоггеров говорили о функциональном характере Linq, и если вы посмотрите на все предоставленные MS Linq методы, вы обнаружите, что они не производят побочных эффектов. Они дают возвращаемые значения, но они ничего не меняют. Найдите аргументы по методу Linq ForEach, и вы получите хорошее объяснение этой концепции.

Имея это в виду, то, что вы хотите, это примерно так:

var addressItems = company.internalData.Emps.SelectMany(
    emp => emp.privateData.Addresses.SelectMany(
           addr => addr.Items
    )
);
foreach (var item in addressItems)
{
   ...
}

Однако, если вы хотите сделать именно то, что вы просили, то это направление, в котором вам нужно идти:

var addressItems = company.internalData.Emps.SelectMany(
    emp => emp.privateData.Addresses.SelectMany(
           addr => addr.Items.Select(item =>
           { 
              // Do the stuff
              return item;
           }) 
    )
);