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

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

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

Я нашел отличный пример сортировки и группировки с и без LINQ в книге Магенниса, и у него также есть пример написания 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();

Наиболее полезные примеры - это когда вы можете использовать встроенные методы в LINQ, такие как All а также Any, Как это:

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

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

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

упс, 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 потребовалось бы получить такое же ленивое поведение при оценке.

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

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