Сообщество программистов Autodesk в СНГ

Статьи => Тестирование статей => Тема начата: Александр Пекшев aka Modis от 08-11-2017, 15:33:37

Название: [Revit API] Перечисление элементов и приведение к List
Отправлено: Александр Пекшев aka Modis от 08-11-2017, 15:33:37
Сегодня столкнулся с ситуацией когда приведение FilteredElementCollector к List, используя метод расширения ToList(), ускорил работу плагина раз эдак в 500!
Но обо всем по порядку.
Начиная изучать Revit API и плохо зная все основы Net я натолкнулся на статью Перечисление элементов (http://adn-cis.org/perechislenie-elementov.html). Топик немного о другом конечно и я на него не наговариваю. Но дело в том, что в мозгу он как-то отложился и запомнился как "всегда делай итерацию по 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 (https://habrahabr.ru/post/193774/). И еще стоит прочитать про такое предупреждение, выдаваемое Решарпером (https://jetbrains.ru/products/resharper/) - Possible Multiple Enumeration of IEnumerable (https://stackoverflow.com/questions/20129079/resharpers-example-code-for-explaining-possible-multiple-enumeration-of-ienume).
К сожалению в моем коде решарпер не может уловить такую разновидность предупреждения и предательски молчит.

Как это лечится - а лечится это очень просто - использованием 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"!

Надеюсь мой случай окажется полезным и ком-нибудь спасет нервы и время ))
Название: Re: [Revit API] Перечисление элементов и приведение к List
Отправлено: Дмитрий Загорулькин от 09-11-2017, 20:13:17
Подозреваю, что приведение ToArray даст тот же эффект. Т.к. получаемая коллекция впоследствии не изменяется, использование списка вместо массива, на мой взгляд, избыточно (но не критично).
Название: Re: [Revit API] Перечисление элементов и приведение к List
Отправлено: Владимир Шу от 10-11-2017, 08:28:22
Подозреваю, что приведение ToArray даст тот же эффект. Т.к. получаемая коллекция впоследствии не изменяется, использование списка вместо массива, на мой взгляд, избыточно (но не критично).
Угу, даст тот же эффект и избыточности нет =о).
https://github.com/dotnet/coreclr/blob/master/src/mscorlib/shared/System/Collections/Generic/List.cs строка 25 и потом конструктор в 37 строке =о)
Название: Re: [Revit API] Перечисление элементов и приведение к List
Отправлено: Дмитрий Загорулькин от 10-11-2017, 14:20:57
Полезно, однако, исходники смотреть! Получается, что список - просто более удобная обёртка для массива. Я подозревал, теперь буду знать. Спасибо!