Преобразование вложенных циклов foreach в LINQ - PullRequest
3 голосов
/ 12 января 2011

Я написал следующий код, чтобы установить свойства для различных классов.Это работает, но одна из моих новогодних задач - максимально использовать LINQ, и, очевидно, этот код не работает.Есть ли способ переписать его в формате «чистого LINQ», желательно без использования циклов foreach?(Еще лучше, если это можно сделать одним оператором LINQ - подстановки в порядке.)

Я пытался поиграть с join, но это ни к чему не привело, поэтому я прошу ответна этот вопрос - желательно без объяснения, так как я бы предпочел «декомпилировать» решение, чтобы выяснить, как оно работает.(Как вы, наверное, догадываетесь, я сейчас гораздо лучше читаю LINQ, чем пишу, но я намерен это изменить ...)

 public void PopulateBlueprints(IEnumerable<Blueprint> blueprints)
 {
   XElement items = GetItems();
   // item id => name mappings
   var itemsDictionary = (
     from item in items
     select new
     {
       Id = Convert.ToUInt32(item.Attribute("id").Value),
       Name = item.Attribute("name").Value,
     }).Distinct().ToDictionary(pair => pair.Id, pair => pair.Name);

  foreach (var blueprint in blueprints)
  {
    foreach (var material in blueprint.Input.Keys)
    {
      if (itemsDictionary.ContainsKey(material.Id))
      {
        material.Name = itemsDictionary[material.Id];
      }
      else
      {
        Console.WriteLine("m: " + material.Id);
      }
    }

    if (itemsDictionary.ContainsKey(blueprint.Output.Id))
    {
      blueprint.Output.Name = itemsDictionary[blueprint.Output.Id];
    }
    else
    {
      Console.WriteLine("b: " + blueprint.Output.Id);
    }
  }
}

Ниже приведены определения необходимых классов;они просто контейнеры для данных, и я отбросил все биты, не относящиеся к моему вопросу:

public class Material
{
  public uint Id { get; set; }

  public string Name { get; set; }
}

public class Product
{
  public uint Id { get; set; }

  public string Name { get; set; }
}

public class Blueprint
{
  public IDictionary<Material, uint> Input { get; set; }

  public Product Output { get; set; }
}

Ответы [ 6 ]

6 голосов
/ 12 января 2011

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

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

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

Одна вещь, которую вы могли бы сделать, это дать Blueprint и Product общий интерфейс, содержащий Id и Name. Затем вы можете написать один метод для обновления продуктов и чертежей с помощью itemsDictionary на основе запроса для каждого:

UpdateNames(itemsDictionary, blueprints);
UpdateNames(itemsDictionary, blueprints.SelectMany(x => x.Input.Keys));

...

private static void UpdateNames<TSource>(Dictionary<string, string> idMap,
    IEnumerable<TSource> source) where TSource : INameAndId
{
    foreach (TSource item in source)
    {
        string name;
        if (idMap.TryGetValue(item.Id, out name))
        {
            item.Name = name;
        }
    }
}

Предполагается, что на самом деле не требуется вывод консоли. Если вы это сделаете, вы всегда можете передать соответствующий префикс и добавить блок «else» в метод. Обратите внимание, что я использовал TryGetValue вместо двух поисков в словаре для каждой итерации.

3 голосов
/ 12 января 2011

Я буду честен, я не читал ваш код. Для меня ваш вопрос ответил сам, когда вы сказали «код для установки свойств». Вы не должны использовать LINQ для изменения состояния объектов / наличия побочных эффектов. Да, я знаю, что вы могли бы написать методы расширения, которые бы вызывали это, но вы бы злоупотребляли функциональной парадигмой, разработанной LINQ, и, возможно, создавали бы бремя обслуживания, особенно для других разработчиков, которые, вероятно, не найдут никаких книг. или статьи в поддержку вашей деятельности.

1 голос
/ 12 января 2011

Поскольку вы заинтересованы в том, чтобы делать как можно больше с Linq, вы можете попробовать подключаемый модуль VS ReSharper .Он идентифицирует циклы (или части циклов), которые можно преобразовать в операторы Linq.Он делает кучу других полезных вещей и с Linq.

Например, циклы, значения сумм которых конвертируются для использования Sum, и циклы, которые применяют внутренний фильтр, заменяются на Where.Даже конкатенация строк или другая рекурсия на объекте преобразуется в Aggregate.Я узнал больше о Linq, попробовав изменения, которые он предлагает.

Плюс ReSharper также хорош по 1000 другим причинам:)

0 голосов
/ 12 января 2011

Посмотрите, работает ли это

  var res = from blueprint in blueprints
     from material in blueprint.Input.Keys
     join  item in items on 
     material.Id equals Convert.ToUInt32(item.Attribute("id").Value)
     select material.Set(x=> { Name = item.Attribute("id").Value; });

Вы не найдете метод set, для этого создан метод расширения.

 public static class LinqExtensions
    {
        /// <summary>
        /// Used to modify properties of an object returned from a LINQ query
        /// </summary>
        public static TSource Set<TSource>(this TSource input,
            Action<TSource> updater)
        {
            updater(input);
            return input;
        }
    }
0 голосов
/ 12 января 2011

Как насчет этого

    (from blueprint in blueprints
     from material in blueprint.Input.Keys
     where itemsDictionary.ContainsKey(material.Id)
     select new { material, name = itemsDictionary[material.Id] })
     .ToList()
     .ForEach(rs => rs.material.Name = rs.name);

    (from blueprint in blueprints
     where itemsDictionary.ContainsKey(blueprint.Output.Id)
     select new { blueprint, name = itemsDictionary[blueprint.Output.Id] })
     .ToList()
     .ForEach(rs => rs.blueprint.Output.Name = rs.name);
0 голосов
/ 12 января 2011

Как уже говорили другие, вы, вероятно, не хотите делать это без foreach петель.Петли обозначают побочные эффекты, что является главной целью упражнения.Тем не менее, вы все еще можете LINQ это:

  var materialNames =
      from blueprint in blueprints
      from material in blueprint.Input.Keys
      where itemsDictionary.ContainsKey(material.Id)
      select new { material, name = itemsDictionary[material.Id] };

  foreach (var update in materialNames)
      update.material.Name = update.name;

  var outputNames =
      from blueprint in blueprints
      where itemsDictionary.ContainsKey(blueprint.Output.Id)
      select new { blueprint, name = itemsDictionary[blueprint.Output.Id] };

  foreach (var update in outputNames)
      update.Output.Name = update.name;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...