如何使用LINQ来避免嵌套循环?

时间:2011-11-28 20:29:51

标签: c# linq-to-objects

我一直在阅读LINQ to Objects,现在我的同事们希望我把它呈现给他们。

现在,我对运算符和语法选择有了一个很好的理解,但我听说你可以通过LINQ 避免重度嵌套循环。我无法想出一套好的“之前和之后”代码清单来证明这一点。

我在Magennis的书中找到了使用和不使用LINQ进行排序和分组的一个很好的例子,他还有一个编写xml的例子。但那些嵌套循环呢?这是否是一个现实的主张,因为我们通常需要一个或两个foreach循环来迭代查询结果?

如果有人能够向我解释这个想法(理想情况下是具体的例子),我将非常感激。

5 个答案:

答案 0 :(得分:9)

假设您有很多产品,例如:

var products = new List<Product>
    {
        new Product { Id = 1, Category = "Electronics", Value = 15.0 },
        // etc.
    };

并且您想要找到价值&gt;的所有产品$ 100.0,按类别分组,您可以使用foreach执行此操作:

var results = new Dictionary<string, List<Product>>();

foreach (var p in products)
{
    if (p.value > 100.0)
    {
        List<Product> productsByGroup;

        if (!results.TryGetValue(p.Category, out productsByGroup))
        {
            productsByGroup = new List<Product>();
            results.Add(p.Category, productsByGroup);
        }
        productsByGroup.Add(p);
    }
}

或者,您可以简单地使用LINQ方法:

var results = products.Where(prod => prod.Value > 100.0)
                  .GroupBy(prod => prod.Category);

或使用LINQ表达式语法:

var results = from p in products 
                  where p.Value > 100.0
                  group p by p.Category;

更简洁,更不易出错。

答案 1 :(得分:8)

这是一种可以用Linq删除的嵌套循环。

foreach(SomeClass item in Items)
{
    foreach(SomeOtherClass subItem in item.SubItems)
    {
        // ...
    }
}

可以变成:

foreach(SomeOtherClass subItem in Items.SelectMany(i => i.SubItems))
{
}

使用the SelectMany extension method on IEnumerable

这个非常有用的地方是nested loop double-break scenarios

答案 2 :(得分:2)

var results = new List<Object>();
foreach(var i in list)
{
    if (i.property == value)
    {
         foreach(var j in list.SubList)
         {
              if (j.other == something)
              {
                  results.push(j);
              }
         }
    }
}

可能是:

var results = list.Where(i => i == value)
                  .SelectMany(i => i.SubList)
                  .Where(j => j.other == something)
                  .ToList();

答案 3 :(得分:1)

这是一个有点人为的例子。

假设您获得了一个字符串列表,您的任务是找到并返回HashSet<>中这些字符串中的所有控制字符。

var listOStrings = new List<string> { ... };
var result = new HashSet<char>();

您可能会这样做:

foreach (var str in listOStrings)
{
    foreach (var c in str)
    {
        if (Char.IsControl(c))
        {
            result.Add(c);
        }
    }
}

或使用LINQ:

result = new HashSet<char>(
    listOStrings
        .SelectMany(str => str.Where(Char.IsControl)));

答案 4 :(得分:1)

最有用的示例是您可以在LINQ中使用内置方法,例如AllAny。像这样:

bool hasCats = listOfAnimals.Any(animal => animal.Type == "Cat");

使用带有if和break和bool检查变量的for循环写一下,我想这至少会有五行代码来做同样的事情。嗯,让我们看看:

bool hasCats = false;
foreach(Animal animal in listOfAnimals)
{
    if (animal.Type == "Cat")
    {
        hasCats = true;
        break;
    }
}

ooops,9行。你需要仔细阅读至少三个以了解代码的作用。

嗯,更多相同。假设哺乳动物有一个真实的类型层次。

IEnumerable<Cat> allCats = listOfAnimals.OfType<Cat>();

这将返回所有可以转换为Cat的动物,并将它们返回,投放并准备使用。用循环写的:

List<Cat> allCats = new List<Cat>();
foreach(var animal in listOfAnimals)
{
    var cat = animal as Cat;
    if (cat != null)
    {
        allCats.Add(cat);
    }
}

老实说,你应该将其分解为一个单独的方法,并使用yield return cat;来获得与LINQ版本相同的惰性行为。

但我更喜欢查询语法。只需极少的噪音即可阅读。

var cats = 
    from cat in listOfCats
    where cat.Age > 5
    where cat.Color == "White"
    select cat;

使用普通循环编写

List<Cat> cats = new List<Cat>();
foreach(Cat cat in listOfCats)
{
    if (cat.Age > 5)
    {
        if (cat.Color == "White")
        {
            cats.Add(cat);
        }
    }
}

再次需要使用yield return的单独方法来获得相同的延迟评估行为。