Я долго думал, с какой статьи начать перевод. Потом, вспомнив, что в основном моя агитация направлена на Дельфистов, принял решение проводить все описания в плане "Delphi for Win32 vs. Delphi Prism", а уже когда материал подобного рода иссякнет, заняться взглядами под другим углом.
Вот в каком порядке переводить раздел
Delphi Prism Language, была проблема уже нешуточная. В конце концов, посоветовавшись с товарищами, я решил идти более-менее по логическому порядку, но писать всё-таки не учебник по программированию, а считать, что читают статью люди, имеющие опыт в данной отрасли. Проще говоря, это будет всё тот же "Delphi for Win32 vs. Delphi Prism", упорядоченный от простейших материалов к интереснейшим :-)
Прежде всего стоит сделать важное замечание. Delphi Prism - .NET-язык, то есть полностью CLR-совместимый. Это значит, что любое изменение и любая мелочь языка должны быть построены в строгом соответствии со стандартом M$ - иначе программа даже просто не скомпилируется. Поэтому, когда я буду говорить о проведённых изменениях, очень многие из них я смогу мотивировать только одной фразой:
потому что .NET. Это касается и имён конструкторов, и целочисленного деления, и ещё кучки разных тонкостей, каждую из которых попытались или включить в опции совместимости (как с конструкторами), или обыграть так, чтобы для программиста ничего не поменялось, а компилятор сам проводил преобразования в корректный IL (как с границами массивов - в .NET они начинаются с 0, но в Delphi Prism реализовали, конечно же, привычный нам свободный вариант... но об этом в соответствующем месте), или попросту много раз громко сакцентировать внимание на
переделке (как с целочисленным делением). По ходу статьи вы встретите следующие пометки:
...
, потому что .NET. — особенно краткое оправдание для некоторых изменений.
Несовместимо! — акцент на кардинальных нововведениях.
Совместимо:) — далее следует инструкция по включению обратной совместимости для описываемого пункта.
Вообще говоря, в 99% случаев все синтаксические проблемы по переходу решаются с помощью
Oxidizer.
1. Пространства имён.Начать следует с нескольких концепций .NET.
*Ох, чует моё сердце, за время написания цикла статей по Призме я этих концепций расскажу на пол-МСДН, так что даже первый раз увидевшие платформу в глаза смогут свободно писать на ней тяжеловезные фреймворки сразу после изучения основ синтаксиса произвольного языка) Но, может, это и к лучшему. ОК, поехали.*Понятия "файл" для программиста не существует. Файлы - это сущность чисто физическая, о ней вспоминаешь, когда сохраняешь проект на жёстком диске, а программа должна работать с сущностями разработки, с контекстом и задачей приложения. Поэтому разбиения на модули нет. Есть разбиение на
пространства имён (ПИ). Пространство имён распространяется на несколько файлов, имеет название, ключевое слово
namespace заменило собою ключевое слово
unit, с той лишь разницей, что его название может повторяться в нескольких файлах - тогда это одно и то же ПИ.
Совместимо:) - ключевое слово
unit осталось как синоним для
namespace.
Delphi Prism полностью поддерживает циклические ссылки, в отличие от Delphi for Win32. То есть приведённый ниже код скомпилируется без проблем:
namespace FirstOne;
interface
uses SecondOne;
type
TClass1 = class
private
C1: Class2;
end;
//...
implementation
end.
namespace SecondOne;
interface
uses FirstOne;
type
Class2 = class(Class1)
end;
//...
implementation
end.
Это возможно благодаря многопроходному компилятору (компилятор Delphi, как известно, - однопроходный). Коджировцы утверждают, что компилятор Призмы, несмотря на многопроходность, не заимствовал фольклорных проблем С++ и потери в скорости не ощущаются - из-за отсутствия понятия "заголовочный файл". Что же, посмотрим на практике.
Название ПИ можно опустить:
namespace;
interface
implementation
end.
В таком случае описанные в нём объекты станут глобальными для всей сборки. Тем не менее, такой приём не рекомендуется.
Даже если вы не указали используемое ПИ в секции
uses, то доступ к его элементам всегда можно получить, полностью прописав путь к элементу прямо в коде:
var
PS: System.Drawing.Printing.PaperSource;
Или:
if PS.Kind = System.Drawing.Printing.PaperSourceKind.Upper then //...
Если же прописали, то, понятное дело, выстраивать эти достойные фантазии гигантомана конструкции нет необходимости:)
Структуру проекта принято разделять на иерархию вложенных ПИ, чтобы получалось нечто вроде
namespace System.Text.RegularExpressions. Чтобы использовать сразу все вложенные в какое-то пространство ПИ, можно воспользоваться конструкцией ".*":
uses System.Xml.*;
Кстати говоря: если вы добавляете использование одних и тех же ПИ в каждое пространство вашего проекта, возможно, лучше будет сделать это глобально с помощью "Project Options" в IDE.
2. Главный метод.Как только что выяснилось в ходе разговора о пространствах имён, понятия "программа" (
program) у CLR не существует. Встаёт логичный вопрос: что же тогда служит точкой входа для сборки? Где искать начало выполнения? Ответ: в главном методе.
Несовместимо!Главный метод имеет имя
Main и служит той точкой, с которой начинается выполнение любой программы, будь она консольной, визуальной, или Веб-сервисом (исключение DLL, которая, вообще говоря, не выполняется). Такой метод должен быть хотя бы у одного класса в вашем пространстве имён; если он найден только у одного класса, выполнение автоматически начнётся с данного Main`a, в противном же случае выбирается тот класс, который указан в Project Options.
Главный метод обязательно статичен, то есть, в терминологии Delphi, представляет собою метод класса (
class method). Он может не принимать параметров вообще, иметь один строковый параметр либо иметь один параметр - динамический массив строк. В последних двух случаях Main`у передаются аргументы командной строки.
Главный метод может либо не возвращать значения, либо возвращать целое число - код выхода программы.
Ниже привожу (вернее - честно слямзиваю с Prism Wiki) пример главного метода некоей программы:
class method Program.Main(Args: array of String): Integer;
begin
if Length(Args) <> 1 then begin
Console.WriteLine('Filename expected!');
exit 1;
end;
RunWith(Args[0]);
exit 0;
end;
Здесь, чтобы пояснить небольшой кусочек кода, придётся забежать вперёд и сразу рассказать о нескольких мелочных нововведениях:
- Ключевое слово method означает то, что и означает - любой метод:-) Это функция или процедура, в терминах старого доброго Паскаля... и Делфи, конечно же. Метод может возвращать значение, а может и не возвращать - в этом его преимущество, меняя функцию на процедуру, вам приходилось заодно изменять и ключевое слово в её описании и реализации. Сейчас это неудобство не нужно. Да и термин ООП "метод" лучше объясняет суть явления, чем пришельцы из 70-х "процедура" и "функция".
Совместимо:) Никто не заставляет писать method, старый синтаксис procedure и function остался в целях совместимости и убран никогда не будет. - Ключевое слово exit - дубликат return из языков с Си-подобным синтаксисом. Немедленный выход из метода с возвращением указанного значения соответствующего типа. Или невозвращением - в случае метода-процедуры.
Несовместимо! Exit у Delphi Prism - ключевое слово языка с соответствующим синтаксисом. Функция Exit() с одним необязательным параметром из Delphi for Win32 2009 осталась в прошлом. - Метод WriteLine() класса Console, как нетрудно догадаться, выполняет функции процедуры WriteLn. Впрочем, это уже относится не к Призме, а чисто к FCL, так что лучше MSDN вам никто ничего не объяснит. Тем более что она теперь русская.
В принципе, с этими замечаниями действия, которые выполняет написанный выше Main, становятся очевидными: проверить, передан ли программе ровно один параметр, только тогда начать выполнение основной логики, в противном случае вывести на консоль сообщение об ошибки и вернуть возмущённый код выхода.
3. Переменные.Небольшое отступление от логики. Вообще говоря, повторюсь, если бы это был учебник по программированию на Delphi Prism для начинающих, то я бы пошёл как установлено временем: идентификаторы, выражения, константы, простейшие операторы... а только потом уже такая сложнейшая по своему наполнению область, как использование переменных. Но поскольку статью читают (надеюсь, читают) далеко не нубы, то я перескочу сразу к более интересному материалу, а ценные сведения об основах упомяну тогда, когда они срочно понадобятся по ходу рассказа.
Совместимо:) Переменные в Призме объявляются стандартной конструкцией
var. Другое дело что теперь эта конструкция свободно гуляет по всему вашему коду. В отличие от Delphi for Win32, в Призме объявление переменных доступно не только в описательной секции метода, но и в любой точке его кода. Старый принцип Бейсика и Си: где понадобилось - там и объявляйте. Любить этот принцип или нет, использовать или забыть как страшный сон - решать только вам.
method DoSomething;
var
someInt1, someInt2: Integer;
someFloat: Double := 0.5;
begin
//немного кода
var someBoolean: Boolean;
//и ещё немного кода
var AnswerToLifeTheUniverseAndEverything: Integer := 42;
end;
Несовместимо! Обратите внимание: начальная инициализация переменных происходит не символом "
=", а оператором присваивания "
:=". По мнению разработчиков Delphi Prism, такой синтаксис корректнее иллюстрирует суть процесса: присваивание стартового значения переменной (полю, свойству... да мало ли), а не статическое, постоянное равенство. Это же вам не константа.
Инициализация переменных одновременно с объявлением - возможность, доступная для любого типа данных. Вот вам создание объекта:
method DeclareTwoFiles;
var
File1: System.IO.FileStream := new FileStream('c:\file1.txt', FileMode.Append, FileAccess.Write, FileShare.Write);
begin
var File2: System.IO.FileStream := new FileStream('c:\file2.txt', FileMode.Append, FileAccess.Write, FileShare.Write);
end;
Да, господа, это вызов конструктора. Вот во что он превратился. Конструкторы в Призме безымянные, вернее сказать, их имя совпадает с именем класса
, потому что .NET. Но я дарю вам опять волю к жизни и ставлю яркую пометочку
Совместимо:) - возможность использованния конструкторов вида
.Create включается в Project Options, в настройках обратной совместимости. Но об этом - в своё время, ещё дойдёт у нас речь до конструкторов.
Delphi Prism, будучи дотнет-языком, активно использует автоматическое распознавание типов, более известное как Type Inference:
var x := %101;
var s := 'Hello';
var c := new System.UTF8Encoding(true);
Здесь переменная
x сама по себе получит тип
Byte (как наименьший из возможных), переменная
s - тип
string, а
c -
UTF8Encoding. К сожалению, в настоящий момент это работает только для локальных переменных, - но в том числе и для объявленных "по месту".
(Кстати, вы заметили "
%101"? Это просто пятёрка. В двоичной системе счисления, что указано соответствующим префиксом. Да и старый знакомый префикс "
$" для шестнадцатеричной тоже никуда не делся.)
Любой идентификатор может быть зарезервированным стандартным словом, если не забывать везде ставить перед ним символ "&":
var &End: Boolean;
Более того, когда идентификатор, как название члена, следует после
точки, амперсанд и тот можно опустить - компилятор Призмы понимает, что здесь может быть только пользовательское название, но никак не ключевое слово языка.
Что-то статья получилась о ерунде всякой. Пожалуй, на третью часть надо пустить что-нибудь действительно покрасивей: паралелльные циклы, там... или асинхронное выполнение. Или про обнуляемые типы можно поговорить. Посмотрим через пару дней.