Как использовать LINQ, чтобы избежать вложенных циклов?

Я читал о LINQ для объектов, и теперь мои коллеги хотят, чтобы я представил их им.

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

Я нашел отличный пример сортировки и группировки с LINQ и без него в книге Magennis, и он также есть пример написания xml. Но как насчет вложенных циклов? Это даже реалистичное утверждение, учитывая, что нам обычно нужно foreach цикл или два для итерации результатов запроса в любом случае?

Если кто-нибудь может объяснить мне эту идею (в идеале с конкретными примерами), я был бы очень признателен.

5 ответов


скажем, у вас есть много продуктов, таких как:

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

и вы хотите найти все продукты со значением > $ 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;

гораздо более краткой и менее подверженным ошибкам.


вот тип вложенного цикла, который вы можете удалить с помощью Linq.

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

его можно превратить в:

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

используя на SelectMany метод расширения на IEnumerable.

одно место, где это очень полезно для вложенные циклические сценарии двойного разрыва.


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

вот несколько надуманный пример.

Предположим, вам дали список строк, и ваша задача заключалась в том, чтобы найти и вернуть все управляющие символы, найденные в этих строках в 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)));

наиболее полезными примерами являются случаи, когда вы можете использовать встроенные методы в LINQ, например All и Any. Вот так:

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

напишите это с циклом for с if и break и переменной проверки bool, я думаю, что это будет по крайней мере пять строк кода, чтобы сделать то же самое. Хм, давайте посмотрим:

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;

написано с помощью plain петли

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

снова отдельный метод с yield return потребуется, чтобы получить такое же ленивое поведение оценки.