Преобразование типов объектов в Delphi
Хочу рассмотреть один дискуссионный вопрос связанный с преобразованием типов. Сначала краткое введение. В Delphi существует два оператора is и as, связанных с преобразованием типов, и способ преобразования типа с помощью функции с именем типа (класса).
Оператор is используется для проверки типа, если объект соответствует указанному типу или является его наследником, то оператор возвращает True. В случае если в переменной nil то оператор возвращает False.
if Object is TButton then
Оператор as используется для преобразования типа. Если объект соответствует типу или наследнику, то он преобразуется, если нет то выдаётся исключение EInvalidCast. В случае если в переменной nil, то исключение не произойдёт, но, конечно, обратится к полям объекта не получится - будет другое исключение.
Существует второй способ преобразования типа переменной - просто оборачиваем её в функцию с именем нужного класса. В этом случае проверки на тип не происходит. Посмотреть различия в работе можно на следующем коде:
// На форме две кнопки и у них два обработчика, добавленные стандартным способом, больше в проекте ничего нет.
procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage((Sender as TMaskEdit).Text); end;
procedure TForm1.Button2Click(Sender: TObject); begin ShowMessage(TMaskEdit(Sender).Text); end;
А вот и сам вопрос. Предположим мы делаем проверку на тип объекта и потом выполняем с ним какие-то действия. Какой вариант использовать предпочтительнее?
// На форме две кнопки и у них два обработчика, добавленные стандартным способом, больше в проекте ничего нет.
procedure TForm1.Button1Click(Sender: TObject); begin if Sender is TMaskEdit then ShowMessage((Sender as TMaskEdit).Text); end;
procedure TForm1.Button2Click(Sender: TObject); begin if Sender is TMaskEdit then ShowMessage(TMaskEdit(Sender).Text); end;
Некоторые моменты.
Оставим в стороне вопрос о контексте этого условия. Условие по типу это плохой вариант и их следует избегать. Но предположим, что есть необходимость именно в таком условии и выполнении действий с объектами после условия.
Также оставим в стороне вопрос о производительности. В данном случае не принципиально, что будет выполнятся быстрее и медленнее, сможет ли компилятор оптимизировать или нет.
В рассматриваемом вопросе интересует именно читаемость и идиоматичность таких конструкций для Delphi. Есть некоторые за и против двух вариантов.
Вариант с функцией короче (меньше символов) и содержит на одну конструкцию меньше, что делает его проще.
Второй аргумент - вариант с функцией более логичен, сначала проверяем тип, а потом уже используем объект с этим типом без дополнительной проверки. Если рассматривать псевдокод, то варианты выглядят так
Если Sender является TMaskEdit Покажи (Sender как TMaskEdit).Текст, но если Sender не является TMaskEdit выведи исключение
Если Sender является TMaskEdit Покажи (Sender как TMaskEdit).Текст
С другой стороны, возможно, способ с функций больше подходит для преобразования простых типов, а для классов следует всегда использовать as. В документацииopen in new window при описании преобразования типов указано "For information about casting class and interface types, see "The as Operator" in Class References and Interface References (Delphi).". Впрочем, эта предложение, никак не указывает и не намекает на предпочтительность оператора as.
И ещё есть аспект надёжности, особенно в крупном проекте с участием большого количества разработчиков. В этом смысле преимущества у варианта с оператором as. Например, если часть "(Sender as TMaskEdit).Text" будет скопирована в любой другой контекст, можно быть уверенным что объект будет нужного типа или код упадёт с исключением. Если же применяется вариант "TMaskEdit(Sender).Text" то может возникнуть неопределённое поведение. Это похоже на один из доводов в пользу отказа от with.
Буду рад выслушать любые мысли по этому поводу.
Ссылки на документацию