将嵌套的foreach循环转换为LINQ

我编写了以下代码来设置各种类的属性。 它可行,但我的新一年的解决方案之一是尽可能多地使用LINQ,显然这段代码没有。 有没有办法以“纯LINQ”格式重写它,最好不使用foreach循环? (如果可以在单个LINQ语句中完成,则更好 – 子语句很好。)

我试着玩join但这并没有把我带到任何地方,因此我要求回答这个问题 – 最好没有解释,因为我更喜欢“反编译”解决方案以弄清楚它是如何工作的。 (正如你可能猜到的那样,我目前在阅读LINQ方面比写它更好,但我打算改变它…)

  public void PopulateBlueprints(IEnumerable 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 Input { get; set; } public Product Output { get; set; } } 

我不认为这实际上是转换为LINQ的好选择 – 至少目前的forms不是这样。

是的,你有一个嵌套的foreach循环 – 但你在顶级foreach循环中做了其他的事情,所以它不是易于转换的forms, 包含嵌套。

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

可以做的一件事是为BlueprintProduct提供包含IdName的通用界面。 然后,您可以编写一个方法,根据每个查询,通过itemsDictionary更新产品和蓝图:

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

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

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

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

例如,将值总和的循环转换为使用Sum ,并将应用内部filter的循环更改为使用Where 。 甚至对象上的字符串连接或其他递归也会转换为Aggregate 。 我通过尝试它所暗示的变化了解了Linq的更多信息。

加上ReSharper也很棒,还有其他1000多个原因:)

正如其他人所说,如果没有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; 

那这个呢

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

看看这是否有效

  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 { ///  /// Used to modify properties of an object returned from a LINQ query ///  public static TSource Set(this TSource input, Action updater) { updater(input); return input; } }