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

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

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

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

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Сегодня столкнулся с ситуацией когда приведение 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"!

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

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

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Подозреваю, что приведение ToArray даст тот же эффект. Т.к. получаемая коллекция впоследствии не изменяется, использование списка вместо массива, на мой взгляд, избыточно (но не критично).

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

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

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

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Полезно, однако, исходники смотреть! Получается, что список - просто более удобная обёртка для массива. Я подозревал, теперь буду знать. Спасибо!