Как я могу использовать 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)));