将嵌套的foreach循环转换为LINQ

时间:2011-01-12 07:02:42

标签: c# linq foreach

我编写了以下代码来设置各种类的属性。它有效,但我的新一年的解决方案之一是尽可能多地使用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 个答案:

答案 0 :(得分:6)

我认为这实际上不是转换为LINQ的好选择 - 至少不是目前的形式。

是的,你有一个嵌套的foreach循环 - 但是你在顶级foreach循环中做了其他的事情,所以它不是只是包含嵌套的易于转换的形式。 / p>

更重要的是,代码的主体是关于副作用的,无论是写入控制台还是更改您找到的对象中的值。 LINQ很棒,当你有一个复杂的查询,你想循环它依次对每个项目采取行动,可能有副作用......但你的查询不是真的很复杂,所以你不会得到太多的好处。

然后,您可以编写一种方法,根据每个产品的查询,通过Blueprint更新产品和蓝图:

Product

这假设您实际上不需要控制台输出。如果这样做,您可以始终传入适当的前缀并在方法中添加“else”块。请注意,我已经使用Id而不是在每次迭代的字典上执行两次查找。

答案 1 :(得分:3)

老实说,我没看过你的代码。对我来说,当你说“设置属性的代码”时,你的问题就会自动回答。您不应该使用LINQ来改变对象的状态/有副作用。是的,我知道您可以编写会导致这种情况发生的扩展方法,但是您会滥用LINQ所规范的功能范例,并可能造成维护负担,特别是对于可能找不到任何书籍的其他开发人员而言或支持你的努力的文章。

答案 2 :(得分:1)

由于您有兴趣尽可能多地使用Linq,您可能想尝试使用VS插件ReSharper。它将识别可以转换为Linq运算符的循环(或循环的部分)。它也与Linq一起做了很多其他有用的东西。

例如,对值进行求和的循环将转换为使用Sum,而应用内部过滤器的循环将更改为使用Where。甚至对象上的字符串连接或其他递归也会转换为Aggregate。我从Linq那里学到了更多关于Linq的知识。

Plus ReSharper也因其他大约1000个原因而非常棒:)

答案 3 :(得分:0)

正如其他人所说,如果没有foreach循环,您可能不希望这样做。循环表示副作用,这是练习的重点。也就是说,你仍然可以使用它:

  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;

答案 4 :(得分:0)

这个怎么样?

    (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);

答案 5 :(得分:0)

看看是否有效

  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;
        }
    }