Parameter.DisplayUnitType
В одном из своих проектов мне нужно было получить все параметры практически всех элементов в проекте, а также все свойства каждого параметра. Проект может содержать несколько десятков тысяч элементов, а каждый элемент может содержать несколько десятков параметров.
Очевидно, что такой перебор занимает довольно продолжительное время.
Проблема, с которой я столкнулся, было определение свойства DisplayUnitType (Единица измерения) для параметра. Дело в том, что не все параметры имеют единицы измерения. В случае попытки получить свойство DisplayUnitType у параметра, для которого единица измерения не предусмотрена, то Revit выбрасывает исключение Autodesk.Revit.Exceptions.InvalidOperationException. Заранее определить, предусмотрена для параметра единица измерения или нет – нельзя.
Таким образом, приходится перехватывать исключение для каждого параметра с помощью блока try…catch
- foreach (var parameter in element.Parameters)
- {
- try
- {
- var dut = parameter.DisplayUnitType;
- }
- catch (Autodesk.Revit.Exceptions.InvalidOperationException)
- {
- }
- }
Но, перехват исключения - это довольно дорогостоящая операция и занимает относительно продолжительное время. При десятках тысяч итераций, время, затрачиваемое на перехват исключения, может оказаться довольно ощутимым.
Я решил исправить это недоразумение, и выяснить, можно ли определить заранее, предусмотрена для параметра единица измерения или нет.
С помощью бесплатного декомпилятора dotPeek я открыл сборку RevitAPI.dll. Первым делом я решил посмотреть на свойство DisplayUnitType класса Parameter.
- public DisplayUnitType DisplayUnitType
- {
- get
- {
- Definition definition = this.Definition;
- ParamTypeSpec paramTypeSpec1;
- if ((ParamTypeEnum) *(int*)
- definition.getParamTypeSpec(¶mTypeSpec1)
- != (ParamTypeEnum) 15)
- {
- throw new Autodesk.Revit.Exceptions
- .InvalidOperationException(new FunctionId(
- "n:\\build\\2013_ship_x64_inst_20120221_2030\\source\\api\\revitapi\\objects\\parameters\\APIParameter.cpp",
- 581, "Autodesk::Revit::DB::Parameter::DisplayUnitType::get"),
- string.Empty);
- }
- else
- {
- ParamTypeSpec paramTypeSpec2;
- return (DisplayUnitType)
- \u003CModule\u003E.FormatOptions\u002EgetDisplayUnits(
- \u003CModule\u003E.AUnits\u002EgetFormatOptions(
- \u003CModule\u003E.ADocument\u002EgetAUnits(
- (ADocument*) *(long*) this.m_pCDA),
- (UnitType.Enum) *(int*) ((IntPtr)
- definition.getParamTypeSpec(¶mTypeSpec2) + 8L)));
- }
- }
- }
Наиболее интересным здесь является строка if ((ParamTypeEnum) *(int*) definition.getParamTypeSpec(¶mTypeSpec1) != (ParamTypeEnum) 15). Здесь мы видим, что параметр имеет единицу измерения, если Definition.getParamTypeSpec(¶mTypeSpec1) = 15.
Первое что пришло в голову – нужно с помощью рефлексии вызвать метод definition.getParamTypeSpec. Но, этот параметр является внутренним, помечен как unsafe, и к тому же в качестве параметра принимает указатель. К сожалению, моих знаний оказалось недостаточно, для того чтобы работать с такими параметрами с помощью рефлексии и я решил пойти другим путем.
Следующим шагом я решил посмотреть свойство ParameterType класса Definition.
- public override ParameterType ParameterType
- {
- get
- {
- REVIT_MAINTAIN_STATE revitMaintainState;
- \u003CModule\u003E.REVIT_MAINTAIN_STATE\u002E\u007Bctor\u007D(
- &revitMaintainState);
- ParameterType parameterType;
- try
- {
- ParamDef* paramDefPtr = this.m_pParamDef;
- ParamTypeSpec paramTypeSpec;
- \u003CModule\u003E.paramDefToParamType(
- ¶mTypeSpec, paramDefPtr);
- switch (^(int&) @paramTypeSpec)
- {
- case 1:
- parameterType = ParameterType.Text;
- break;
- case 6:
- parameterType = ParameterType.URL;
- break;
- case 9:
- parameterType = ParameterType.YesNo;
- break;
- case 11:
- parameterType = ParameterType.Integer;
- break;
- case 13:
- parameterType = ParameterType.Material;
- break;
- case 14:
- parameterType = ParameterType.FamilyType;
- break;
- case 15:
- switch (^(int&) ((IntPtr) ¶mTypeSpec + 8))
- {
- case 0:
- parameterType = ParameterType.Length;
- break;
- case 1:
- parameterType = ParameterType.Area;
- break;
- case 2:
- parameterType = ParameterType.Volume;
- break;
- case 3:
- parameterType = ParameterType.Angle;
- break;
- case 4:
- parameterType = ParameterType.Number;
- break;
- case 26:
- parameterType = ParameterType.Force;
- break;
- case 27:
- parameterType = ParameterType.LinearForce;
- break;
- case 28:
- parameterType = ParameterType.AreaForce;
- break;
- case 29:
- parameterType = ParameterType.Moment;
- break;
- default:
- parameterType = (ParameterType) (
- ^(int&) ((IntPtr) ¶mTypeSpec + 8) + 100);
- break;
- }
- case 16:
- parameterType = ParameterType.NumberOfPoles;
- break;
- case 24:
- parameterType = ParameterType.FixtureUnit;
- break;
- case 30:
- parameterType = ParameterType.LoadClassification;
- break;
- default:
- parameterType = ParameterType.Invalid;
- break;
- }
- }
- __fault
- {
- \u003CModule\u003E.___CxxCallUnwindDtor(
- (__FnPtr<void (void*)>) __methodptr(REVIT_MAINTAIN_STATE\u002E\u007Bdtor\u007D),
- (void*) &revitMaintainState);
- }
- \u003CModule\u003E.REVIT_MAINTAIN_STATE\u002E\u007Bdtor\u007D(
- &revitMaintainState);
- return parameterType;
- }
- }
- }
Здесь мы видим, что если paramTypeSpec = 15, то ParameterType является Длиной, Площадью и т.д. Таким образом, для того, чтобы определить, если ли у параметра единица измерения, достаточно проверить свойство ParameterType. Если это свойство равно Length, Area и т.д., то параметр имеет единицу измерения. В противном случае – нет.
Важно также обратить внимание на строку
- default:
- parameterType = (ParameterType) (
- ^(int&) ((IntPtr) ¶mTypeSpec + 8) + 100);
- break;
Если посмотреть на определение перечисления ParameterType, то можно сказать, если числовое значение перечисления ParameterType > 100, то параметр также имеет единицу измерения.
В результате, я создал вспомогательный метод-расширение:
- public static class ParameterExtensions
- {
- public static bool HasDisplayUnitType(
- this Parameter parameter)
- {
- var parameterType =
- parameter.Definition.ParameterType;
- switch (parameterType)
- {
- case ParameterType.Length:
- case ParameterType.Area:
- case ParameterType.Volume:
- case ParameterType.Angle:
- case ParameterType.Number:
- case ParameterType.Force:
- case ParameterType.LinearForce:
- case ParameterType.AreaForce:
- case ParameterType.Moment:
- return true;
- }
- /* At the reflector I can see the following code
- default:
- parameterType = (ParameterType)
- (^(int&) ((IntPtr) ¶mTypeSpec
- + 8) + 100);
- break;
- looking at the ParameterType enumeration
- suggests that every parameter type whose
- integer value is greater than 100 belongs
- to paramTypeSpec = 15
- */
- return 100 < (int)parameterType;
- }
- }
Тестирование производительности
Слова об увеличении производительности были бы пустым звуком, без тестирования.
В первую очередь протестируем перехват исключения
- Reference r;
- try
- {
- r = uidoc.Selection.PickObject(
- ObjectType.Element);
- }
- catch (OperationCanceledException)
- {
- message = "Cancelled";
- return Result.Cancelled;
- }
- var e = doc.GetElement(r.ElementId);
- Stopwatch sw = Stopwatch.StartNew();
- foreach (Parameter parameter in e.Parameters)
- {
- try
- {
- DisplayUnitType dut =
- parameter.DisplayUnitType;
- Debug.Print(dut.ToString());
- }
- catch (Autodesk.Revit.Exceptions
- .InvalidOperationException)
- {
- }
- }
- sw.Stop();
- TaskDialog.Show("Benchmark result",
- sw.Elapsed.ToString());
Запускам, выбираем элемент. Результат – 0,41 секунды.
Затем, с использованием моего метода-расширения.
- Reference r;
- try
- {
- r = uidoc.Selection.PickObject(
- ObjectType.Element);
- }
- catch (OperationCanceledException)
- {
- message = "Canceled";
- return Result.Cancelled;
- }
- var e = doc.GetElement(r.ElementId);
- Stopwatch sw = Stopwatch.StartNew();
- foreach (Parameter parameter in e.Parameters)
- {
- if (parameter.HasDisplayUnitType())
- {
- DisplayUnitType dut =
- parameter.DisplayUnitType;
- Debug.Print(dut.ToString());
- }
- }
- sw.Stop();
- TaskDialog.Show("Benchmark result",
- sw.Elapsed.ToString());
Результат – 0,029 секунды на том же самом элементе!
Вы можете сказать, что 0,41 секунды это совсем немного и пользователь даже не заметит этой разницы. Конечно, разница практически не ощущается, когда вы обрабатываете только один элемент. Но, стоит заметить, что перехват исключения работает в 15 раз медленнее.
Обработка 100 элементов займет 41 секунду при использовании блока try…catch. С новым методом – 3 секунды. Согласитесь, разница получается уже значительной.
Автор перевода: Виктор Чекалин
Обсуждение: http://adn-cis.org/forum/index.php?topic=195
Опубликовано 31.08.2013Отредактировано 31.08.2013 в 21:32:09