[Revit API] Перечисление элементов и приведение к List

Автор Тема: [Revit API] Перечисление элементов и приведение к List  (Прочитано 98 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 721
  • Карма: 117
  • Отец modplus.org
Сегодня столкнулся с ситуацией когда приведение FilteredElementCollector к List, используя метод расширения ToList(), ускорил работу плагина раз эдак в 500!
Но обо всем по порядку.
Начиная изучать Revit API и плохо зная все основы Net я натолкнулся на статью Перечисление элементов. Топик немного о другом конечно и я на него не наговариваю. Но дело в том, что в мозгу он как-то отложился и запомнился как "всегда делай итерацию по FilteredElementCollector без предварительного приведения типов". Однако, есть ситуации, когда так делать нельзя и сейчас расскажу пример такой ситуации.
Задача простая - перебираем все листы в документе и получаем данные со штампов (TitleBlock). Вот пример кода, который я использовал изначально:
Код - C# [Выбрать]
  1. private void GetDataFromSheets(Document doc)
  2. {
  3.     // Получаем листы в документе
  4.     IList<Element> viewSheetElements = new FilteredElementCollector(doc).OfClass(typeof(ViewSheet)).ToElements();
  5.     // Получаем все штампы
  6.     FilteredElementCollector titleBlocks = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_TitleBlocks);
  7.     // делаем итерацию по листам
  8.     foreach (var element in viewSheetElements)
  9.     {
  10.         var viewSheetElement = (ViewSheet) element;
  11.         if (!viewSheetElement.IsTemplate && viewSheetElement.ViewType == ViewType.DrawingSheet)
  12.         {
  13.             GetDataFromTitleBlocks(viewSheetElement, titleBlocks);
  14.         }
  15.     }
  16. }
  17.  
  18. private void GetDataFromTitleBlocks(ViewSheet viewSheet, FilteredElementCollector titleBlocks)
  19. {
  20.     foreach (Element element in titleBlocks)
  21.     {
  22.         if (element.OwnerViewId.IntegerValue != viewSheet.Id.IntegerValue) continue;
  23.         if (element is FamilyInstance familyInstance)
  24.         {
  25.             // some code
  26.         }
  27.     }
  28. }
Т.е. для ускорения процесса я сначала получил все TitleBlock в документе, а затем в методе GetDataFromTitleBlocks просто сравниваю OwnerViewId с Id листа. Так было сделано специально, так как при получении FilteredElementCollector для конкретного вида вызывалась перерисовка графики на виде.
Как видите, в методе GetDataFromTitleBlocks ничего не происходит. Если вы запустите этот код с вызовом метода GetDataFromSheets в достаточно пустом документе, то ничего не заметите. Но если запустить этот код в реальном проекте, где уже есть пару сотен листов с кучей видов на них, то вот этот пустой код будет работать очень долго!

Почему так? Я не сразу это понял. И даже когда мне объяснили, то до сих пор не понимаю))
Все дело в том, что FilteredElementCollector реализует интерфейс IEnumerable<Element>. При перечислении FilteredElementCollector для каждого элемента скорее всего вызываются какие-то методы, а в моем примере выше такая итерация происходит для каждого листа в документе. Возможно мое "объяснение" не совсем верное, поэтому вот ссылка на статью на Хабре - Проблемы использования IEnumerable. И еще стоит прочитать про такое предупреждение, выдаваемое Решарпером - Possible Multiple Enumeration of IEnumerable.
К сожалению в моем коде решарпер не может уловить такую разновидность предупреждения и предательски молчит.

Как это лечится - а лечится это очень просто - использованием List вместо FilteredElementCollector. Вношу небольшие изменения в начальный код:
Код - C# [Выбрать]
  1. private void GetDataFromSheets(Document doc)
  2. {
  3.     // Получаем листы в документе
  4.     IList<Element> viewSheetElements = new FilteredElementCollector(doc).OfClass(typeof(ViewSheet)).ToElements();
  5.     // Получаем все штампы
  6.     List<Element> titleBlocks = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_TitleBlocks).ToList();
  7.     // делаем итерацию по листам
  8.     foreach (var element in viewSheetElements)
  9.     {
  10.         var viewSheetElement = (ViewSheet) element;
  11.         if (!viewSheetElement.IsTemplate && viewSheetElement.ViewType == ViewType.DrawingSheet)
  12.         {
  13.             GetDataFromTitleBlocks(viewSheetElement, titleBlocks);
  14.         }
  15.     }
  16. }
  17.  
  18. private void GetDataFromTitleBlocks(ViewSheet viewSheet, List<Element> titleBlocks)
  19. {
  20.     foreach (Element element in titleBlocks)
  21.     {
  22.         if (element.OwnerViewId.IntegerValue != viewSheet.Id.IntegerValue) continue;
  23.         if (element is FamilyInstance familyInstance)
  24.         {
  25.             // some code
  26.         }
  27.     }
  28. }
Пробую - и ура - код срабатывает моментально!

Ко всему вышесказанному вспоминается высказывание (точно не скажу откуда) "Если не знаете что использовать, то используйте List"!

Надеюсь мой случай окажется полезным и ком-нибудь спасет нервы и время ))
ModPlus
Отвечаю в надежде получить плюсики в карму =))

Онлайн Дмитрий Загорулькин

  • ADN
  • *
  • Сообщений: 1236
  • Карма: 236
  • LISP/C#, AutoCAD/Civil 3D
  • Skype: zagor_dmtr
Подозреваю, что приведение ToArray даст тот же эффект. Т.к. получаемая коллекция впоследствии не изменяется, использование списка вместо массива, на мой взгляд, избыточно (но не критично).

Оффлайн Владимир Шу

  • ADN Club
  • ****
  • Сообщений: 330
  • Карма: 54
Подозреваю, что приведение ToArray даст тот же эффект. Т.к. получаемая коллекция впоследствии не изменяется, использование списка вместо массива, на мой взгляд, избыточно (но не критично).
Угу, даст тот же эффект и избыточности нет =о).
https://github.com/dotnet/coreclr/blob/master/src/mscorlib/shared/System/Collections/Generic/List.cs строка 25 и потом конструктор в 37 строке =о)

Онлайн Дмитрий Загорулькин

  • ADN
  • *
  • Сообщений: 1236
  • Карма: 236
  • LISP/C#, AutoCAD/Civil 3D
  • Skype: zagor_dmtr
Полезно, однако, исходники смотреть! Получается, что список - просто более удобная обёртка для массива. Я подозревал, теперь буду знать. Спасибо!