Всем привет! Сегодня разберёмся что такое LINQ-запросы и лямбда-выражения, а также узнаем, как с их помощью упростить свой код в плагинах для Revit.
Введение
LINQ, что означает Language Integrated Query, то есть интегрированный язык запросов, появился в C# достаточно давно. Его методы находятся в пространстве имён Syctem.LINQ, и представляют собой богатый набор инструментов для работы с коллекциями.
Общий смысл здесь такой: у нас есть какой-то большой список, и нам нужно быстро получить из него особым образом обработанные данные. Допустим, это список чисел, и нам надо найти максимум. в стандартной ситуации мы напишем так:
var integers = new int[10];
var i = int.MinValue;
foreach (var number in integers)
{
if (number > i) i = number;
}
И получим максимум в переменной i. Ну, в принципе, нормально. Если несколько раз надо так сделать, то можно и отдельный метод придумать. Если в разных проектах, то и отдельную библиотеку можно сделать, чтобы не писать каждый раз. В общем, так и сделали и появилась библиотека LINQ, и теперь этот код выглядит так:
var integers = new int[10];
var i = integers.Max();
Какие тут преимущества:
- Краткость кода
- Обобщённость метода (из списка int получим int, из списка других объектов — этот объект).
- Оптимальный алгоритм реализации. Нам не надо задумываться о том, не будет ли тормозить наш код, или искать ошибки в нём. За нас уже всё написали и проверили.
- Ленивое вычисление. Часть методов организована так, что вычисление значений производится не сразу, а только тогда, когда мы к ним обратимся. Это ускоряет код
LINQ-методы
Разберём методы, которые я наиболее часто применяю при написании плагинов:
- Where. Берёт из коллекции только те элементы, которые соответствуют определённому условию.
- OrderBy. Сортирует элементы по возрастанию, необходимо передавать функцию сортировки
- OrderByDescending. Тоже самое, но по убыванию
- Cast<T>. Приводит элементы к типу T. Если какой-то элемент окажется не T, то выбрасывает исключение.
- OfType<T>. Безопасно приводит элементы к типу T (без исключений). Если вы однозначно уверены, что неправильных типов не будет, то Cast отработает быстрее.
- FirstOrDefault. Возвращает первый элемент последовательности или null, если его нет. Можно передать условие, и тогда вернёт первый соответствующий условию элемент
- First. Тоже самое, но если элемента нет, кидает исключение. Стараюсь не использовать, просто говорю о разнице.
- Select. Приводит элементы к нужному типу с помощью определённого правила. Простейший пример: с его помощью можно превратить ElementId в Element, если передать функцию, выполняющую такое действие.
- Count. Возвращает число элементов в последовательности. Можно передать условие, и вернёт число соответствующих условию элементов.
- ToList. Превращает последовательность в список.
Лямбда-выражения и использование методов LINQ на практике
Выше я много говорил о передаче условий в методы LINQ. Собственно, эти условия и есть, как правило, лямбда-выражения. Но что это такое?
Лямбда-выражения — короткие анонимные (не имеющие имени) функции, которые пишутся прямо там, где вызываются. Синтакcис у них такой:
var i = integers.Where(j => { return j > 0; });
Где: j => { return j > 0; } — и есть наша лямбда.
Она не обязана быть в одну строку:
var i = integers.Where(j =>
{
//some code
return j > 0;
});
Так тоже можно. Но где же упрощение?
Если лямбда однострочная, то можно опустить фигурные скобки и слово return. Тогда получим такое выражение:
var i = integers.Where(j => j > 0);
И вот с этого момента всё становится гораздо понятнее. Нужно взять из списка только те цифры j, которые больше 0.
На самом деле, если навести на Where в Visual Studio, то увидим такое:
В метод Where мы передаём делегат Func, который принимает int (на самом деле он принимает T, просто для каждого T будет указан конкретный тип, и у нас сейчас это int), и возвращает bool. Соответственно, если элемент последовательности, пройдя через функцию, вернёт true, то он попадёт в результат, иначе нет.
Пример из Revit API:
var elements = new Element[10];
var selected = elements.Where(element => element.Name == "My element");
Более сложный пример:
var elements = new Element[10];
var selected = elements.Where(element => doc.GetElement(element.GetTypeId()).Name == "My type");
Тут мы у каждого элемента берём его тип и проверяем имя типа (не советую именно так делать, берите сразу типы, но иногда помогает).
Полезные практики
Если вам нужен один элемент, напишите FirstOrDefault(lambda) вместо Where(lambda).FirstOrDefault(). Вам нет смысла перебирать все элементы, чтобы получить нужный вам любой один.
var elements = new Element[10];
var selected = elements.FirstOrDefault(element => element.Name == "My element");
Тоже самое касается метода Count. Если нам нужно подсчитать число элементов, то просто пишем так:
var elements = new Element[10];
var selected = elements.Count(element => element.Name == "My element");
Как использовать Select:
Допустим, какой-то метод вернул вам коллекцию ElementId. Вам нужно взять из неё только определённые элементы. Пишем так:
var elementIds = new ElementId[10];
var selected = elementIds.
Select(id => doc.GetElement(id)) // превратили в элемент
.Where(element => element.Name == "My element");
Как использовать Cast<T>:
var walls = new FilteredElementCollector(doc).
OfClass(typeof(Wall))
.Cast<Wall>();
Получили список объектов класса Wall.
На этом пока всё. Пишите в комментарии, если я забыл указать что-то важное, и подписывайтесь на мой телеграм-канал о Revit API и до новых встреч!