Обзор Visual Basic 9.0Источник: MSDN Magazine Эрик Мейер (Erik Meijer), Аманда Силвер (Amanda Silver) и Пол Вик (Paul Vick), Корпорация Майкрософт
Оглавление
ВведениеVisual Basic всегда предназначался для создания практичных бизнес-приложений, работающих с данными. Если переход на .NET предоставил разработчикам преимущества единой и управляемой платформы, следующий выпуск Visual Basic включает набор функций, значительно влияющих на производительность при создании работающих с данными приложений. Эти расширения языка предлагают средства запросов общего назначения, применимые ко всем источникам данных: реляционным, графам иерархических объектов или XML-документам. Данная статья является обзором этих новых функций. Дополнительные сведения, в том числе обновления языка и предварительные версии компилятора Visual Basic, доступны в Visual Basic Developer Center (http://msdn.microsoft.com/vbasic/default.aspx). Знакомство с Visual Basic 9.0Чтобы увидеть преимущества этих функций языка на практике, начнем с реального примера - базы данных ЦРУ CIA World Factbook. В ней содержится различная географическая, экономическая, социальная и политическая информация о государствах мира. В нашем примере начнем со схемы для названия государства и его столицы, общей площади и численности населения. Эта схема представлена на языке Visual Basic 9.0 с помощью следующего класса (для краткости используется псевдокод):
Ниже представлен небольшой набор базы данных государства, который будет использован в качестве рабочего примера:
Можно выполнить поиск всех государств в этом списке с населением менее одного миллиона человек, используя следующий запрос:
Поскольку только в Мадагаскаре более одного миллиона жителей, приведенная выше скомпилированная и выполненная программа распечатает следующий список названий государств:
Давайте рассмотрим программу подробнее, чтобы понять функции Visual Basic 9.0, значительно упростившие её написание. Прежде всего, в объявлении выражений для стран Countries, используется новый синтаксис объекта-инициализатора, New Country With {..., .Area = 458, ...}. В нем создается экземпляр сложного объекта с помощью понятного синтаксиса, сходного с существующим утверждением With, и с применением выражения. Объявление также иллюстрирует использование неявно типизированных локальных переменных, в которых компилятор выводит тип локальной переменной Countries из выражения инициализатора с правой стороны объявления. Приведенное выше объявление является точным эквивалентом объявления явно выраженной локальной переменной типа Country():
Повторяем, это все еще сильно типизированное объявление; компилятор автоматически вывел тип из правой стороны локального объявления, и программисту не требуется вводить этот тип в программу вручную. Объявление локальной переменной SmallCountries инициализируется с помощью выражения запроса в формате SQL, в котором выбираются все государства с населением менее миллиона человек. Сходство с SQL является намеренным, что позволяет программистам, уже знакомым с SQL, начать работу с Visual Basic более быстро:
Обратите внимание, что в этом примере представляет другое применение явной типизации: компилятор выводит тип SmallCountries как IEnumerable(Of Country), исходя их типа результата выполняемого запроса. Компилятор преобразовывает само выражение запроса в вызовы интерфейса API с поддержкой LINQ, реализующего операторы запросов для всех типов, реализующих IEnumerable(Of T). В этом случае результат преобразования прост:
В расширенном синтаксисе используются лямбда-функции, вставленные непосредственно в текст и возвращающие результат выражения. Лямбда-функция преобразуется в делегат и передается в функцию Where, которая определяется в библиотеке операторов стандартных запросов как расширение интерфейса IEnumerable(Of T) Мы рассмотрели некоторые новые свойства Visual Basic 9.0, теперь приступим к более детальному обзору. Неявно типизированные локальные переменныеВ объявлении неявно типизированных локальных переменных их тип выводится из выражения-инициализатора в правой части локального объявления. Например, компилятор выводит типы следующих переменных объявлений:
Поэтому они будут точными эквивалентами следующих явно типизированных объявлений:
Поскольку типы объявлений локальных переменных выводятся с помощью нового режима Option Infer On (включен по умолчанию для новых проектов) независимо от значения Option Strict, доступ к таким выражениям всегда имеет раннюю привязку. В Visual Basic 9.0 позднюю привязку можно указать явно, объявляя переменные типа Object, например так:
Вычисление типов предотвращает случайное использование поздней привязки и, что более важно, позволяет расширить привязку на новые типы данных, например, на XML, что будет рассмотрено ниже. Переменная управления циклом в утверждении For...Next или For Each...Next также может быть неявно типизированной переменной. При выборе переменной управления циклом, как в For I = 0 To SmallCountries.Count, или в For Each country In smallCountries, идентификатор создает новую неявно типизированную локальную переменную в пределах цикла, тип которой выводится из инициализатора или выражения коллекции. Применяя эту возможность вывода типов, можно переписать цикл, печатающий малые страны:
Тип country выводится из Country, типа элемента в SmallCountries. Инициализаторы объектов и массивовВ Visual Basic оператор With упрощает доступ к нескольким членам составного объекта, для чего не надо каждый раз писать имя объекта. Внутри оператора With выражение, начинающееся с точки, расценивается как случай, когда перед точкой стоит имя целевого объекта оператора With. Например, следующие операторы инициализируют новый экземпляр Country и последовательно инициализируют поля следующих значений:
Новые инициализаторы объектов в Visual Basic 9.0 - это формы на основе With с использованием выражений. Они упрощают создание экземпляров сложных объектов. С из помощью можно свести два приведенных выше оператора к одному (неявно типизированному) локальному объявлению:
Возможность использовать выражение для инициализации объекта важна для запросов. Обычно запрос выглядит как объявление объекта, инициализированного выражением Select справа от знака равенства. Поскольку условие Select возвращает выражение, следует иметь возможность инициализировать целый объект одним выражением. Как мы видим, инициализаторы объектов также пригодны для создания наборов сложных объектов. Можно инициализировать массивы и вывести типы элементов с помощью выражения инициализатора массива. Например, если города объявлены как классы,
мы в нашем примере стран можем создать массив столиц:
Анонимные типыЧасто требуется удалить или пропустить в результатах запроса некоторые члены типа. Например, нам можем потребоваться узнать Name и Country для всех тропических столиц. Чтобы выяснить, являются ли они тропическими, используем столбцы исходных данных Latitude и Longitude (широта и долгота), но в конечном результате эта цифирь ни к чему. В Visual Basic 9.0 для этого мы создаем новый экземпляр объекта (без именования типа) для каждой столицы, широта которой попадает в диапазон между северным тропиком (Рака) и южным (Козерога):
Выведенный тип локальной переменной tropical - набор экземпляров анонимного типа, а именно (используя псевдо-код) IEnumerable(Of { Name As String, Country As String }). Компилятор Visual Basic создаст неявно описанный класс, например, _Name_As_String_Country_As_String_, названия и тип членов которого выводятся из инициализатора объекта:
Внутри одной программы компилятор объединит идентичные анонимные типы. Два инициализатора анонимных объектов, в которых указана одинаковая последовательность свойств имен и типов в том же порядке, будут создавать экземпляры одного и того же анонимного типа. Анонимные типы в Visual Basic создаются на основе Object, который позволяет компилятору передавать их как аргументы и результаты функций. Поскольку анонимные типы обычно используются для отбора существующих типов, Visual Basic 9.0 позволяет использовать синтаксис New With { city.Name, city.Country } как упрощение для New With { .Name = city.Name, .Country = city.Country }. При использовании такой нотации в составе запроса её можно сделать ещё короче:
Обе эти сокращенные формы идентичны полной форме, приведенной выше. Глубокая поддержка XMLLINQ to XML - новый API-интерфейс для обработки XML в памяти, специально созданный для воплощения новейших возможностей .NET Framework, таких, как Language-Integrated Query framework. Подобно тому как расширения в обработке запросов дают простой и удобный синтаксис по сравнению с базовыми возможностями .NET Framework, в Visual Basic 9.0 добавлена глубокая поддержка LINQ to XML, с помощью XML-литералов и XML-свойств. Чтобы проиллюстрировать работу с XML-литералами, построим запрос к плоским реляционным источникам данных Countries и Capitals, и создадим иерархическую XML-структуру, в которой столица каждой страны будет храниться как дочерний элемент и расчет плотности населения как атрибут. Чтобы найти столицу данной страны, мы выполняем соединение имени-члена каждой страны со страной-членом каждого города. Получив страну и ее столицу, мы легко можем сформировать XML-фрагмент, написав выражение для подстановки. Для выражения Visual Basic подстановка пишется в синтаксисе, напоминающем ASP: Name=<%= country.Name %> или <Name><%= city.Name %></Name>. Вот это запрос, сочетающий XML-литералы и работу с данными:
Тип XElement можно опустить из описания, в этом случае он будет выводиться, как это происходит для любого локального объявление. В этом объявлении результат запроса Select замещает структуру внутри элемента <Countries>. Запрос Select - это вычисляемое выражение первого уровня и он обозначен привычными для стиля ASP тегами<%= и %> в <Countries>. Поскольку результатом выполнения запроса Select будет являться выражение, и XML-литералы - тоже выражения, внутри Select допустимо. таким образом, этот вложенный литерал сам содержит вложенные выражения для атрибутов для названий и координат столицCountry.Name, и Country.Population/Country.Area. При компиляции и запуске приведенный запрос возвращает следующий XML-документ (слегка измененный по сравнению с форматированием в IDE для экономии места):
Visual Basic 9.0 компилирует XML-литералы в обычные объекты System.Xml.Linq, обеспечивая полную совместимость между Basic и другими языками, использующими LINQ to XML. В этом примере код, созданный компилятором (если его можно было бы увидеть), будет следующим:
Кроме создания XML, Visual Basic 9.0 также упрощает доступ к XML-структурам через XML-свойства; это означает, что в период выполнения идентификаторы кода Visual Basic привязываются к соответствующим атрибутам и элементам. Например, плотность населения всех стран примера можно напечатать следующим способом:
Если эти возможности соединить вместе, код резко сократится и упростится:
Компилятору известно, что можно использовать позднюю привязку к обычным объектам, если в объявлении, присваивании или инициализации целевое выражение имеет тип Object, а не более специфический тип. Точно так же компилятору известно, что следует использовать привязку поверх XML, если целевое выражение - тип или набор XElement, XDocument, или XAttribute. Как результат позднего привязывания поверх XML, компилятор транслирует это все следующим образом:
Улучшения запросовОператоры запроса - это операторы Select, Order By или Where, работающие со значениями по всему набору данных. Выражение запроса - это серия операторов запросов, имеющих дело с конкретным набором. Например, следующее выражение запроса читает набор стран и возвращает названия всех стран с населением менее миллиона человек:
Синтаксис выражения запроса приближен к стандартному реляционному синтаксису языка SQL, поэтому любой знакомый с ним способен создавать такие выражения после минимального обучения. В то же время синтаксис не ограничен SQL, и не все выражения запроса являются трансляцией из SQL в Visual Basic. Поскольку SQL разрабатывался исходя из чисто реляционной модели, некоторые из его концепций не работают в системе типов, в основе которой лежит иерархия. Кроме того, некоторые элементы синтаксиса и семантики SQL конфликтуют или не взаимодействуют с существующей семантикой и синтаксисом Visual Basic. Таким образом, хотя выражения запроса должны быть знакомы всем, кто знает SQL, необходимо изучить и их отличия. Выражения запроса транслируются в вызовы к базовым операторам выборки данных для типов, к которым возможен запрос, и которые указаны как исходные типы в условии From. Поскольку операторы выборки данных обычно определяются для исходных типов как методы расширения , эти типы можно связать с любыми имеющимися в системе операторами. Из этого вытекает, что при импорте той или иной конкретной реализации синтаксис выражений запроса снова может быть привязан к различным API-интерфейсам LINQ. Таким путем выражения запроса могут опять быть привязаны к реализациям LINQ к SQL или LINQ к объектам (локальный модуль обработки запросов, выполняющий их в памяти). В некоторых операторах запросов, таких как From, Select и Group By, вводятся специальные виды локальных переменных, называемые range variables - переменными диапазона. По умолчанию область действия такой переменной простирается от оператора, где она введена, до оператора, который ее скрывает; она представляет свойство или столбец отдельной строки в коллекции при вычислении запроса. Например, в следующем запросе:
в операторе From вводится переменная диапазона country типа «Country». В следующем операторе запроса Where она использована, чтобы обозначить каждую индивидуальную запись в условии country.Population < 1000000. В некоторых операторах запроса, например, Distinct, переменные управления не используются или не меняются. В других операторах, таких как Select, прекращает действие текущих переменных диапазона и вводят новые переменные диапазона. Например, в запросе:
Оператор запроса Order By имеет доступ только к переменным Name и Pop, введенным оператором Select; если оператор Order By попытается сослаться на country, при компиляции возникнет ошибка. Если запрос завершается без оператора Select, тип полученного элемента коллекции будет таким, как если бы в выборке Select присутствовали все переменные управления из этой области видимости:
Итоговый тип в этом определении будет (в псевдо-коде, чтобы показать анонимный тип) IEnumerable(Of { Country As Country, City As City }). Чем выражения запроса отличаются от SQL: гибкость и иерархические данныеСтруктура выражений запроса в Visual Basic 9.0 является чрезвычайно гибкой. Усовершенствованные операторы можно произвольно добавлять и вкладывать друг в друга. Это упрощает понимание большого запроса, так как можно анализировать его отдельные подвыражения, а также отслеживание типов. В то же время такой принцип отличается от запросов в SQL, где они анализируются как единый блок. Кроме того, в API-интерфейсы с поддержкой LINQ стремятся использовать операторы с отложенным (deferred) выполнением. Отложенное выполнение означает, что запрос не будет обрабатываться, пока не будет обращения к результатам. Для LINQ to SQL это означает, что запрос не будет передан в SQL, пока не будут затребованы результаты. Это значит, что разделение запросов на отдельные операторы не означает многократного запроса к базе. В результате то, что в SQL обычно является вложенным запросом, в LINQ становится гибким запросом. Одна из причин, почему в SQL отсутствует структурная гибкость - основная реляционная модель данных сама по себе слабо структурирована. Например, таблицы не могут содержать подтаблицы, другими словами, все таблицы должны быть плоскими. В результате вместо дробления сложных выражений на более мелкие единицы программисты SQL пишут монолитные выражения, результатом которых являются плоские таблицы, соответствующие модели данных SQL. Поскольку Visual Basic основан на системе типов CLR, не существует ограничений, какие типы могут выступать как компоненты других типов. Помимо статических правил типизации, не существует ограничений на тип выражений, выступающих компонентами других выражений. В результате не только строки, объекты и XML-структуры, но и Active Directory, файлы, элементы реестра и тому подобное - полноправные представители как источников, так и результатов запроса. Операторы запросаТе, кто хорошо знаком с SQL, узнают в основных операторах .NET, работающих с наборами данных, операции реляционной алгебры. В их числе - проекция, выборка, логическое пересечение, группировка и сортировка и они соответствуют модели в обработчике запросов.
Полный синтаксис всех операторов можно найти в описании языка, но для иллюстрации приведем следующий пример. В нем находятся столицы каждого государства и названия стран расставляются по широте расположения столицы:
Для запросов, вычисляющих скалярное значение на основании коллекции, используется оператор Aggregate. В следующем запросе в одном операторе определяется количество малых стран и подсчитывает плотность их населения:
Функции обработки наиболее часто появляются в сочетании с разбиением исходной коллекции. Например, можно сгруппировать все страны на основании критерия, являются ли они тропическими, и затем сосчитать их в каждой группе. Для этого эти операторы можно использовать в сочетании с условиями Group By и Group Join. В приведенном ниже примере вспомогательная функция IsTropical проверяет, является ли климат в City тропическим:
Имея такую функцию, выполним ту же выборку, как и выше, но вначале поделим коллекцию пар Country и Capital на группы, для которых Country.IsTropical будет одинаковым. В этом случае будет две таких группы: в одну попадают тропические страны Палау, Белиз и Мадагаскар; в другую - нетропическая Монако.
Затем посчитаем численность населения и среднюю плотность. Результат будет коллекцией пар Total As Integer и Density As Double:
Предыдущий запрос скрывает достаточную сложность. Запрос ниже выдает тот же результат с помощью лямбда-выражений и методов расширения (method-call syntax):
Методы расширения и лямбда-выраженияМощь инфраструктуры запросов в .NET Framework во многом обеспечивается за счет методов расширения и лямбда-выражений. Методы расширения - общедоступны и снабжены специальными атрибутами, что позволяет запускать их как методы экземпляров. Большинство методов расширения имеют одинаковые форматы вызова. Первый аргумент - экземпляр объекта, с которым работает метод, второй - выражение. Например, метод Where, в который транслируется условие Where, имеет следующий формат вызова:
Так как многие стандартные операторы запроса, такие, как Where, Select, SelectMany и так далее, определяются как методы расширения, в которые передаются делегаты вида Func(Of S,T), компилятор отходит от требования создавать делегаты для предикатов. Вместо этого делегаты, которые захватывают окружающий контекст, и передают их в вызываемый метод. Например, с помощью следующего запроса компилятор создает лямбда-выражения, являющиеся делегатами для передачи в функции Select и Where:
Компилятор создает два лямбда-выражения для источника информации и предиката соответственно:
И предыдущий запрос транслируется в вызовы методов к стандартной инфраструктуре запросов, а источник и лямбда-выражение передаются как аргументы:
Типы, принимающие значения NUllВ реляционных базах данных имеется специальная семантика для значений "null", которая часто несовместима с обычными языками программирования и незнакома программистам. В приложениях, работающих с данными, жизненно необходимо грамотно и точно обращаться с этой семантикой. В .NET Frameworks 2.0 CLR добавлена поддержка null-значений с помощью обобщённого типа Nullable(Of T As Structure). С его помощью можно объявить null-версии для размерных типов Integer, Boolean, Date и так далее. По причинам, которые скоро будут понятными, синтаксис Visual Basic для обнуляемых типов следующий: T?. Например, можно добавить новый член к классу Country, в котором будет записана дату получения страной независимости:
Но не все страны являются суверенными. Дата получения независимости Палау #10/1/1994#, а Британсике Виргинские острова - территория, зависимая от Соединенного Королевства, и поэтому в качестве даты независимости нужно написать Nothing.
Visual Basic 9.0 будет поддерживать троичную логику и null-арифметику при обработке null-величин. Это означает, что если один из операндов арифметической, сравнительной, логической или побитовой операции, сдвига, строки или типа имеет тип Nothing, результатом тоже будет являться Nothing. Если оба операнда имеют некоторое значение, операция выполняется для основных типов операндов и результат преобразуется обратно в null-тип. Поскольку и Palau.Independence и VirginIslands.Independence имеют тип Date?, компилятор будет использовать null-арифметику для приведенных ниже вычитаний, и поэтому подразумеваемый тип логических переменных PLength и VILength будет TimeSpan?.
Значение PLength равно 3980.00:00:00, так как ни один из операндов не равен Nothing. С другой стороны, поскольку значение VirginIslands.Independence равно Nothing, результат опять будет типа TimeSpan?, но значение VILength будет Nothing из-за правил обработки null:
Так же, как в SQL, операторы сравнения обрабатывают значения типа null, и в логических операторах будует использоваться троичная логика. В утверждениях If и While значение Nothing будет интерпретироваться как False; следовательно, в следующем примере кода будет выбрана ветвь Else :
Учтите, что для троичной логики результатом проверки равенств X = Nothing и Nothing = X всегда будет Nothing; для проверки, является ли X Nothing, мы должны использовать логическое сравнение X Is Nothing или Nothing Is X. Нестрогие делегатыПри создании делегатов с помощью AddressOf или Handles в Visual Basic 8.0, методы, которые с ними работают, должны иметь такую же сигнатуру, что и тип, для которого создается делегат. В приведенном ниже примере сигнатура подпрограммы OnClick должна точно соответствовать сигнатуре делегата обработки события Delegate Sub EventHandler(sender As Object, e As EventArgs), который объявляется в типе Button:
В то же время при вызове функций и подпрограмм, не являющихся делегатами, Visual Basic не требует точного соответствия фактических аргументов одному из методов, который мы пытаемся вызвать. Как показано в следующем примере, можно вызвать подпрограмму OnClick с фактическими аргументами типа Button и типа MouseEventArgs, которые являются, соответственно, производными от Object и EventArgs:
И наоборот, допустим, мы определяем подпрограмму RelaxedOnClick, в которую передаются два параметра Object, и затем мы можем вызвать ее с фактическими аргументами типа Object и EventArgs:
В Visual Basic 9.0 привязка к делегатам ослаблена, чтобы соответствовать вызовам методов. Это означает, что если возможно вызвать функцию или подпрограмму с фактическими аргументами, которые точно соответствуют формальным параметрам и возвращаемым типам делегата, такой вызов допустим. Другими словами, привязка и определение делегатов будет подчиняться той же логике перегрузки и приведения типов, что и вызовы этого методов. Из этого следует, что в Visual Basic 9.0 мы можем привязать подпрограмму RelaxedOnClick, в которую передаются два параметра Object, к событию Click объекта Button:
В обработчиках событий редко используются оба аргумента sender и EventArgs. Обычно обработчик получает доступ к состоянию элемента управления, которому принадлежит событие, и игнорирует эти два аргумента. Для поддержки этой обычной ситуации, правила для делегатов можно ослабить настолько, что не передавать в них аргументы вообще, если при этом не возникает неопределенность. Другими словами, можно просто написать следующее:
Понятно, что упрощение делегатов также применяется при создании делегатов, использующих AddressOf или выражения, даже если группа методов является вызовами с поздней привязкой:
ЗаключениеVisual Basic 9.0 унифицирует доступ к данным независимо от их источника: к реляционным базам данных, XML-документам, или произвольным графам объектов, постоянно присутствующих или хранящихся в памяти. Унификация затрагивает стили, приемы, средства и модели программирования. Особо гибкий синтаксис Visual Basic упрощает добавление к языку расширений, таких, как XML-литералы и SQL-подобные выражения запросов. За счет того, что чужой синтаксис переносится из строковых констант в язык Visual Basic, удается значительно сократить прямую работу с новыми API-интерфейсами .NET при запросах, упростить работу за счет использования IntelliSense и смарт-тегов, и значительно улучшить отладку и проверку при компиляции. Такие возможности, как вычисление типов, инициализаторы объектов и нестрогие делегаты, резко снижают избыточность кода, и количество исключений из правил, которые необходимо изучить и помнить программистам, не снижая при этом производительность приложений. Хотя может показаться, что список новых функций в Visual Basic 9.0 длинен, мы надеемся, что рассмотренные выше темы убедят вас в том, что уместно, своевременно и правильно будет назвать Visual Basic лучшим языком программирования в мире. Мы надеемся, что разбудили ваше воображение и что вы согласитесь с тем, что впереди нас ждут ещё более замечательные вещи. |