Обзор 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 с помощью следующего класса (для краткости используется псевдокод):

Class Country
  Public Property Name As String
  Public Property Area As Long 
  Public Property Population As Integer
End Class

Ниже представлен небольшой набор базы данных государства, который будет использован в качестве рабочего примера:

Dim countries = { 
  New Country With { .Name = "Palau", .Area = 458, .Population = 16952 }, _
  New Country With { .Name = "Monaco", .Area = 1.9, .Population = 31719 }, _
  New Country With { .Name = "Belize", .Area = 22960, .Population = 219296 }, _
  New Country With { .Name = "Madagascar", .Area = 587040, .Population =
 13670507}}

Можно выполнить поиск всех государств в этом списке с населением менее одного миллиона человек, используя следующий запрос:

Dim smallCountries = From country In countries _
                     Where country.Population < 1000000 _
Select country
 
For Each country In SmallCountries
  Console.WriteLine(country.Name)
Next

Поскольку только в Мадагаскаре более одного миллиона жителей, приведенная выше скомпилированная и выполненная программа распечатает следующий список названий государств:

Palau
Monaco
Belize 

Давайте рассмотрим программу подробнее, чтобы понять функции Visual Basic 9.0, значительно упростившие её написание. Прежде всего, в объявлении выражений для стран Countries, используется новый синтаксис объекта-инициализатора, New Country With {..., .Area = 458, ...}. В нем создается экземпляр сложного объекта с помощью понятного синтаксиса, сходного с существующим утверждением With, и с применением выражения.

Объявление также иллюстрирует использование неявно типизированных локальных переменных, в которых компилятор выводит тип локальной переменной Countries из выражения инициализатора с правой стороны объявления. Приведенное выше объявление является точным эквивалентом объявления явно выраженной локальной переменной типа Country():

Dim countries As Country() = {...}

Повторяем, это все еще сильно типизированное объявление; компилятор автоматически вывел тип из правой стороны локального объявления, и программисту не требуется вводить этот тип в программу вручную.

Объявление локальной переменной SmallCountries инициализируется с помощью выражения запроса в формате SQL, в котором выбираются все государства с населением менее миллиона человек. Сходство с SQL является намеренным, что позволяет программистам, уже знакомым с SQL, начать работу с Visual Basic более быстро:

Dim smallCountries = From country In Countries _
                     Where country.Population < 1000000 _
   Select country

Обратите внимание, что в этом примере представляет другое применение явной типизации: компилятор выводит тип SmallCountries как IEnumerable(Of Country), исходя их типа результата выполняемого запроса. Компилятор преобразовывает само выражение запроса в вызовы интерфейса API с поддержкой LINQ, реализующего операторы запросов для всех типов, реализующих IEnumerable(Of T). В этом случае результат преобразования прост:

Dim smallCountries As IEnumerable(Of Country) = _
   Countries.Where(Function(country) country.Population < 1000000). 
             Select(Function(country) country)

В расширенном синтаксисе используются лямбда-функции, вставленные непосредственно в текст и возвращающие результат выражения. Лямбда-функция преобразуется в делегат и передается в функцию Where, которая определяется в библиотеке операторов стандартных запросов как расширение интерфейса IEnumerable(Of T)

Мы рассмотрели некоторые новые свойства Visual Basic 9.0, теперь приступим к более детальному обзору.

Неявно типизированные локальные переменные

В объявлении неявно типизированных локальных переменных их тип выводится из выражения-инициализатора в правой части локального объявления. Например, компилятор выводит типы следующих переменных объявлений:

Dim population = 31719
Dim name = "Belize"
Dim area = 1.9
Dim country = New Country With  .Name = "Palau", ...

Поэтому они будут точными эквивалентами следующих явно типизированных объявлений:

Dim population As Integer = 31719
Dim name As String = "Belize"
Dim area As Float = 1.9
Dim country As Country = New Country With { .Name = "Palau", ...}

Поскольку типы объявлений локальных переменных выводятся с помощью нового режима Option Infer On (включен по умолчанию для новых проектов) независимо от значения Option Strict, доступ к таким выражениям всегда имеет раннюю привязку. В Visual Basic 9.0 позднюю привязку можно указать явно, объявляя переменные типа Object, например так:

Dim country As Object = New Country With  .Name = "Palau", ... }

Вычисление типов предотвращает случайное использование поздней привязки и, что более важно, позволяет расширить привязку на новые типы данных, например, на XML, что будет рассмотрено ниже.

Переменная управления циклом в утверждении For...Next или For Each...Next также может быть неявно типизированной переменной. При выборе переменной управления циклом, как в For I = 0 To SmallCountries.Count, или в For Each country In smallCountries, идентификатор создает новую неявно типизированную локальную переменную в пределах цикла, тип которой выводится из инициализатора или выражения коллекции. Применяя эту возможность вывода типов, можно переписать цикл, печатающий малые страны:

For Each country In smallCountries
Console.WriteLine(country.Name)Next

Тип country выводится из Country, типа элемента в SmallCountries.

Инициализаторы объектов и массивов

В Visual Basic оператор With упрощает доступ к нескольким членам составного объекта, для чего не надо каждый раз писать имя объекта. Внутри оператора With выражение, начинающееся с точки, расценивается как случай, когда перед точкой стоит имя целевого объекта оператора With. Например, следующие операторы инициализируют новый экземпляр Country и последовательно инициализируют поля следующих значений:

Dim palau As New Country()
With palau 
   .Name = "Palau" 
  .Area = 458
  .Population = 16952
End With

Новые инициализаторы объектов в Visual Basic 9.0 - это формы на основе With с использованием выражений. Они упрощают создание экземпляров сложных объектов. С из помощью можно свести два приведенных выше оператора к одному (неявно типизированному) локальному объявлению:

Dim palau = New Country With { _
  .Name = "Palau", _
  .Area = 458, _
  .Population = 16952 _
}

Возможность использовать выражение для инициализации объекта важна для запросов. Обычно запрос выглядит как объявление объекта, инициализированного выражением Select справа от знака равенства. Поскольку условие Select возвращает выражение, следует иметь возможность инициализировать целый объект одним выражением.

Как мы видим, инициализаторы объектов также пригодны для создания наборов сложных объектов. Можно инициализировать массивы и вывести типы элементов с помощью выражения инициализатора массива. Например, если города объявлены как классы,

Class City
  Public Property Name As String
  Public Property Country As String
  Public Property Longitude As Long 
  Public Property Latitude As Long
End Class

мы в нашем примере стран можем создать массив столиц:

Dim Capitals = { _
  New City With { _
    .Name = "Antanarivo", _
    .Country = "Madagascar", _
    .Longitude = 47.4, _
    .Latitude = -18.6 }, _
  New City With { _
    .Name = "Belmopan", _
    .Country = "Belize", _
    .Longitude = -88.5, _
    .Latitude = 17.1 }, _
  New City With { _
    .Name = "Monaco", _
    .Country = "Monaco", _
    .Longitude = 7.2, _
    .Latitude = 43.7 }, _
  New City With { _
    .Country = "Palau",
    .Name = "Koror", _
    .Longitude = 135, _
    .Latitude = 8 } _
}

Анонимные типы

Часто требуется удалить или пропустить в результатах запроса некоторые члены типа. Например, нам можем потребоваться узнать Name и Country для всех тропических столиц. Чтобы выяснить, являются ли они тропическими, используем столбцы исходных данных Latitude и Longitude (широта и долгота), но в конечном результате эта цифирь ни к чему. В Visual Basic 9.0 для этого мы создаем новый экземпляр объекта (без именования типа) для каждой столицы, широта которой попадает в диапазон между северным тропиком (Рака) и южным (Козерога):

Const TropicOfCancer = 23.5
Const TropicOfCapricorn = -23.5
Dim tropical = From city In Capitals _ 
       Where TropicOfCancer <= city.Latitude _
            AndAlso city.Latitude >= TropicOfCapricorn _
       Select New With city.Name, city.Country

Выведенный тип локальной переменной tropical - набор экземпляров анонимного типа, а именно (используя псевдо-код) IEnumerable(Of { Name As String, Country As String }). Компилятор Visual Basic создаст неявно описанный класс, например, _Name_As_String_Country_As_String_, названия и тип членов которого выводятся из инициализатора объекта:

Class _Name_As_String_Country_As_String_
    Public Property Name As String
    Public Property Country As String
    ...
End Class

Внутри одной программы компилятор объединит идентичные анонимные типы. Два инициализатора анонимных объектов, в которых указана одинаковая последовательность свойств имен и типов в том же порядке, будут создавать экземпляры одного и того же анонимного типа. Анонимные типы в Visual Basic создаются на основе Object, который позволяет компилятору передавать их как аргументы и результаты функций.

Поскольку анонимные типы обычно используются для отбора существующих типов, Visual Basic 9.0 позволяет использовать синтаксис New With { city.Name, city.Country } как упрощение для New With { .Name = city.Name, .Country = city.Country }. При использовании такой нотации в составе запроса её можно сделать ещё короче:

Dim Tropical = From city In Capitals _
               Where TropicOfCancer <= city.Latitude _
                   AndAlso city.Latitude >= TropicOfCapricorn _
               Select city.Name, city.Country

Обе эти сокращенные формы идентичны полной форме, приведенной выше.

Глубокая поддержка XML

LINQ 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-литералы и работу с данными:

Dim countriesWithCapital As XElement = _
    <Countries>
    <%= From country In Countries, city In Capitals _
        Where country.Name = city.Country _
        Select <Country Name=<%= country.Name %>
                        Density=<%= country.Population / country.Area %>>
                  <Capital>
                   <Name><%= city.Name %></Name>
                   <Longitude><%= city.Longitude %></Longitude>
                   <Latitude><%= city.Latitude %></Latitude>
                  </Capital>
               </Country> _
    %>
    </Countries>

Тип XElement можно опустить из описания, в этом случае он будет выводиться, как это происходит для любого локального объявление.

В этом объявлении результат запроса Select замещает структуру внутри элемента <Countries>. Запрос Select - это вычисляемое выражение первого уровня и он обозначен привычными для стиля ASP тегами<%= и %> в <Countries>. Поскольку результатом выполнения запроса Select будет являться выражение, и XML-литералы - тоже выражения, внутри Select допустимо. таким образом, этот вложенный литерал сам содержит вложенные выражения для атрибутов для названий и координат столицCountry.Name, и Country.Population/Country.Area.

При компиляции и запуске приведенный запрос возвращает следующий XML-документ (слегка измененный по сравнению с форматированием в IDE для экономии места):

<Countries>
 <Country Name="Palau" Density="0.037117903930131008">
   <Capital>
     <Name>Koror</Name><Longitude>135</Longitude><Latitude>8</Latitude>
   </Capital>
 </Country>
 <Country Name="Monaco" Density="16694.21052631579">
   <Capital>
     <Name>Monaco</Name><Longitude>7.2</Longitude><Latitude>3.7</Latitude>
   </Capital>
 </Country>
 <Country Name="Belize" Density="9.5512195121951216">
   <Capital>
     <Name>Belmopan</Name><Longitude>-88.5</Longitude><Latitude>17.1</Latitude>
   </Capital>
 </Country>
 <Country Name="Madagascar" Density="23.287181452711909">
   <Capital>
     <Name>Antananarivo</Name>
     <Longitude>47.4</Longitude><Latitude>-18.6</Latitude>
   </Capital>
  </Country>
</Countries>

Visual Basic 9.0 компилирует XML-литералы в обычные объекты System.Xml.Linq, обеспечивая полную совместимость между Basic и другими языками, использующими LINQ to XML. В этом примере код, созданный компилятором (если его можно было бы увидеть), будет следующим:

Dim countriesWithCapital As XElement = _ 
  New XElement("Countries", _
        From country In Countries, city In Capitals _
        Where country.Name = city.Country _
  Select New XElement("Country", _
             New XAttribute("Name", country.Name), _
             New XAttribute("Density", country.Population/country.Area), _
             New XElement("Capital", _
               New XElement("Name", city.Name), _
               New XElement("Longitude", city.Longitude), _
               New XElement("Latitude", city.Latitude))))

Кроме создания XML, Visual Basic 9.0 также упрощает доступ к XML-структурам через XML-свойства; это означает, что в период выполнения идентификаторы кода Visual Basic привязываются к соответствующим атрибутам и элементам. Например, плотность населения всех стран примера можно напечатать следующим способом:

  • использовать ветвь countriesWithCapital.<Country>, чтобы получить из XML-структуры countriesWithCapital все элементы "Country";
  • использовать ветвь атрибута country.@Density , чтобы получить атрибут "Density" элемента Country;
  • использовать ветвь порожденных объектов country...<Latitude>- записанные как троеточие в исходном коде, чтобы получить все дочерние элементы "Latitude" элемента Country независимо то глубины расположения в иерархии
  • использовать свойство расширения .Value применительно к IEnumerable(Of XElement) для выбора значения первого элемента итоговой последовательности или индексатор расширение (i) для выбора i-го элемента.

Если эти возможности соединить вместе, код резко сократится и упростится:

For Each country In countriesWithCapital.<Country>
  Console.WriteLine("Density = " & country.@Density)
  Console.WriteLine("Latitude = " & country...<Latitude>.Value)
Next

Компилятору известно, что можно использовать позднюю привязку к обычным объектам, если в объявлении, присваивании или инициализации целевое выражение имеет тип Object, а не более специфический тип. Точно так же компилятору известно, что следует использовать привязку поверх XML, если целевое выражение - тип или набор XElement, XDocument, или XAttribute.

Как результат позднего привязывания поверх XML, компилятор транслирует это все следующим образом:

  • выражение countriesWithCapital.<Country> транслируется в необработанный вызов LINQ to XML countriesWithCapital.Elements("Country"), который возвращает коллекциювсех дочерних элементов с именем "Country" элемента Country;
  • выражение атрибута country.@Density транслируется в Country.Attribute("Density").Value, которое возвращает из Country единственный дочерний атрибут с именем "Density";
  • выражение порожденного объекта country...<Latitude> транслируется в вызов LINQ к XML country.Descendants("Latitude"), возвращающий набор всех элементов с этим именем любой глубины ниже country.

Улучшения запросов

Операторы запроса - это операторы Select, Order By или Where, работающие со значениями по всему набору данных. Выражение запроса - это серия операторов запросов, имеющих дело с конкретным набором. Например, следующее выражение запроса читает набор стран и возвращает названия всех стран с населением менее миллиона человек:

Dim smallCountries = From country In Countries _
             Where country.Population < 1000000 _
             Select country

Синтаксис выражения запроса приближен к стандартному реляционному синтаксису языка SQL, поэтому любой знакомый с ним способен создавать такие выражения после минимального обучения. В то же время синтаксис не ограничен SQL, и не все выражения запроса являются трансляцией из SQL в Visual Basic. Поскольку SQL разрабатывался исходя из чисто реляционной модели, некоторые из его концепций не работают в системе типов, в основе которой лежит иерархия. Кроме того, некоторые элементы синтаксиса и семантики SQL конфликтуют или не взаимодействуют с существующей семантикой и синтаксисом Visual Basic. Таким образом, хотя выражения запроса должны быть знакомы всем, кто знает SQL, необходимо изучить и их отличия.

Выражения запроса транслируются в вызовы к базовым операторам выборки данных для типов, к которым возможен запрос, и которые указаны как исходные типы в условии From. Поскольку операторы выборки данных обычно определяются для исходных типов как методы расширения , эти типы можно связать с любыми имеющимися в системе операторами. Из этого вытекает, что при импорте той или иной конкретной реализации синтаксис выражений запроса снова может быть привязан к различным API-интерфейсам LINQ. Таким путем выражения запроса могут опять быть привязаны к реализациям LINQ к SQL или LINQ к объектам (локальный модуль обработки запросов, выполняющий их в памяти).

В некоторых операторах запросов, таких как From, Select и Group By, вводятся специальные виды локальных переменных, называемые range variables - переменными диапазона. По умолчанию область действия такой переменной простирается от оператора, где она введена, до оператора, который ее скрывает; она представляет свойство или столбец отдельной строки в коллекции при вычислении запроса. Например, в следующем запросе:

Dim smallCountries = From country In Countries _
                     Where country.Population < 1000000 _
                     Select country

в операторе From вводится переменная диапазона country типа «Country». В следующем операторе запроса Where она использована, чтобы обозначить каждую индивидуальную запись в условии country.Population < 1000000.

В некоторых операторах запроса, например, Distinct, переменные управления не используются или не меняются. В других операторах, таких как Select, прекращает действие текущих переменных диапазона и вводят новые переменные диапазона. Например, в запросе:

Dim smallCountries = From country In Countries _
                     Select country.Name, Pop = country.Population
                     Order By Pop

Оператор запроса Order By имеет доступ только к переменным Name и Pop, введенным оператором Select; если оператор Order By попытается сослаться на country, при компиляции возникнет ошибка.

Если запрос завершается без оператора Select, тип полученного элемента коллекции будет таким, как если бы в выборке Select присутствовали все переменные управления из этой области видимости:

Dim countriesWithCapital = From country In Countries, city In Capitals _
                           Where country.Name = city.Country

Итоговый тип в этом определении будет (в псевдо-коде, чтобы показать анонимный тип) 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, работающих с наборами данных, операции реляционной алгебры. В их числе - проекция, выборка, логическое пересечение, группировка и сортировка и они соответствуют модели в обработчике запросов.

  • В операторе From вводятся одна или более временных переменных (range variable) и либо указывается коллекция для запроса, либо вычисляется выражение для временной переменной.
  • В операторе Select указывается вид выходной коллекции.
  • Операторы Where и Distinct накладывают условия отбора.
  • Оператор Order By задает способ сортировки.
  • Операторы Skip, Skip While, Take и Take While возвращают подмножество из коллекции с учетом сортировки или условия.
  • Операторы Union, Union All, Except, и Intersect объединяют две коллекции в одну.
  • Оператор Group By группирует коллекцию на основе одного или нескольких ключей.
  • Операторы Avg, Sum, Count, Min, и Max обрабатывают коллекцию и выдают значение.
  • Операторы Any и All анализируют коллекцию и возвращают логическое булево значение для некоторого условия.
  • Оператор Join объединяет две коллекции в одну на основе соответствия ключей, полученных из элементов.
  • Оператор Group Join выполняет групповое объединение двух коллекций на основе соответствия ключей, полученных из элементов.

Полный синтаксис всех операторов можно найти в описании языка, но для иллюстрации приведем следующий пример. В нем находятся столицы каждого государства и названия стран расставляются по широте расположения столицы:

Dim countriesWithCapital = _
  From country In Countries _
  Join city In Capitals On country.Name Equals city.Country _
  Order By city.Latitude _
  Select country.Name

Для запросов, вычисляющих скалярное значение на основании коллекции, используется оператор Aggregate. В следующем запросе в одном операторе определяется количество малых стран и подсчитывает плотность их населения:

Dim popInfo = _
  Aggregate country In Countries _
  Where country.Population < 1000000 _
  Into Total = Count(), Density = Average(country.Population/country.Area)

Функции обработки наиболее часто появляются в сочетании с разбиением исходной коллекции. Например, можно сгруппировать все страны на основании критерия, являются ли они тропическими, и затем сосчитать их в каждой группе. Для этого эти операторы можно использовать в сочетании с условиями Group By и Group Join. В приведенном ниже примере вспомогательная функция IsTropical проверяет, является ли климат в City тропическим:

Function IsTropical() As Boolean
    Return TropicOfCancer =< Me.Latitude AndAlso Me.Latitude >= TropicOfCapricorn
  End Function

Имея такую функцию, выполним ту же выборку, как и выше, но вначале поделим коллекцию пар Country и Capital на группы, для которых Country.IsTropical будет одинаковым. В этом случае будет две таких группы: в одну попадают тропические страны Палау, Белиз и Мадагаскар; в другую - нетропическая Монако.

Табл. 1. Параметры объекта CustomTaskPane
Условие Страна Столица
Country.IsTropical() = True
 
Palau
 
Koror
 
 
 
Belize
 
Belmopan
 
 
 
Madagascar
 
Antanarivo
 
Country.IsTropical() = False
 
Monaco
 
Monaco
 

Затем посчитаем численность населения и среднюю плотность. Результат будет коллекцией пар Total As Integer и Density As Double:

Dim countriesByClimate = _
  From country In Countries _
 Join city In Capitals On country.Name Equals city.Country _
 Group By country.IsTropical()
 Into Total = Count(), Density = Average(country.Population/country.Area)

Предыдущий запрос скрывает достаточную сложность. Запрос ниже выдает тот же результат с помощью лямбда-выражений и методов расширения (method-call syntax):

Dim countriesByClimate7 = _  countries. _
    SelectMany( _
      Function(country) Capitals, _ 
      Function(country, city) New With {country, city}). _
    Where(Function(it) it.country.Name = it.city.Country). _ 
    GroupBy( _
      Function(it) it.city.IsTropical(), _
      Function(IsTropical, Group) _
        New With { _
          IsTropical, _
          .Total = Group.Count(), _
          .Density = Group.Average( _
             Function(it) it.country.Population / it.country.Area _
          ) _
        } _
    )

Методы расширения и лямбда-выражения

Мощь инфраструктуры запросов в .NET Framework во многом обеспечивается за счет методов расширения и лямбда-выражений. Методы расширения - общедоступны и снабжены специальными атрибутами, что позволяет запускать их как методы экземпляров. Большинство методов расширения имеют одинаковые форматы вызова. Первый аргумент - экземпляр объекта, с которым работает метод, второй - выражение. Например, метод Where, в который транслируется условие Where, имеет следующий формат вызова:

Module IEnumerableExtensions
  <Extension> _
  Function Where (Of TSource) _
    (Source As IEnumerable(Of TSource), _
     predicate As Func(Of TSource, Boolean)) As IEnumerable(Of TSource)
    ...
  End Function
End Module

Так как многие стандартные операторы запроса, такие, как Where, Select, SelectMany и так далее, определяются как методы расширения, в которые передаются делегаты вида Func(Of S,T), компилятор отходит от требования создавать делегаты для предикатов. Вместо этого делегаты, которые захватывают окружающий контекст, и передают их в вызываемый метод. Например, с помощью следующего запроса компилятор создает лямбда-выражения, являющиеся делегатами для передачи в функции Select и Where:

Dim smallCountries = From country In countries _
                     Where country.Population < 1000000 _
                     Select country.Name

Компилятор создает два лямбда-выражения для источника информации и предиката соответственно:

Function(Country As Country) country.Name
Function (Country As Country) country.Population < 1000000

И предыдущий запрос транслируется в вызовы методов к стандартной инфраструктуре запросов, а источник и лямбда-выражение передаются как аргументы:

Dim smallCountries = _
  Enumerable.Select( _
      Enumerable.Where(countries, _
        Function (country As Country) country.Population < 1000000), _
        Function(country As Country) country.Name)

Типы, принимающие значения NUll

В реляционных базах данных имеется специальная семантика для значений "null", которая часто несовместима с обычными языками программирования и незнакома программистам. В приложениях, работающих с данными, жизненно необходимо грамотно и точно обращаться с этой семантикой. В .NET Frameworks 2.0 CLR добавлена поддержка null-значений с помощью обобщённого типа Nullable(Of T As Structure). С его помощью можно объявить null-версии для размерных типов Integer, Boolean, Date и так далее. По причинам, которые скоро будут понятными, синтаксис Visual Basic для обнуляемых типов следующий: T?.

Например, можно добавить новый член к классу Country, в котором будет записана дату получения страной независимости:

Partial Class Country
  Public Property Independence As Date?
End Class

Но не все страны являются суверенными. Дата получения независимости Палау #10/1/1994#, а Британсике Виргинские острова - территория, зависимая от Соединенного Королевства, и поэтому в качестве даты независимости нужно написать Nothing.

Dim palau = _
  New Country With { _
    .Name = "Palau", _
    .Area = 458, _
    .Population = 16952, _
    .Independence = #10/1/1994# }
 
Dim virginIslands = _
  New Country With { _
    .Name = "Virgin Islands", _
    .Area = 150, _
    .Population = 13195, _
    .Independence = Nothing }

Visual Basic 9.0 будет поддерживать троичную логику и null-арифметику при обработке null-величин. Это означает, что если один из операндов арифметической, сравнительной, логической или побитовой операции, сдвига, строки или типа имеет тип Nothing, результатом тоже будет являться Nothing. Если оба операнда имеют некоторое значение, операция выполняется для основных типов операндов и результат преобразуется обратно в null-тип.

Поскольку и Palau.Independence и VirginIslands.Independence имеют тип Date?, компилятор будет использовать null-арифметику для приведенных ниже вычитаний, и поэтому подразумеваемый тип логических переменных PLength и VILength будет TimeSpan?.

Dim pLength = #8/24/2005# - Palau.Independence          ‘ 3980.00:00:00

Значение PLength равно 3980.00:00:00, так как ни один из операндов не равен Nothing. С другой стороны, поскольку значение VirginIslands.Independence равно Nothing, результат опять будет типа TimeSpan?, но значение VILength будет Nothing из-за правил обработки null:

Dim vILength = #8/24/2005# - virginIslands.Independence ‘ Nothing

Так же, как в SQL, операторы сравнения обрабатывают значения типа null, и в логических операторах будует использоваться троичная логика. В утверждениях If и While значение Nothing будет интерпретироваться как False; следовательно, в следующем примере кода будет выбрана ветвь Else :

If vILength < TimeSpan.FromDays(10000)  ...
Else
  ...
End if

Учтите, что для троичной логики результатом проверки равенств 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:

Dim WithEvents btn As New Button()Sub OnClick(sender As Object, e As EventArgs) 
Handles B.Click  MessageBox.Show("Hello World from" & btn.Text)End Sub

В то же время при вызове функций и подпрограмм, не являющихся делегатами, Visual Basic не требует точного соответствия фактических аргументов одному из методов, который мы пытаемся вызвать. Как показано в следующем примере, можно вызвать подпрограмму OnClick с фактическими аргументами типа Button и типа MouseEventArgs, которые являются, соответственно, производными от Object и EventArgs:

Dim m As New MouseEventArgs(MouseButtons.Left, 2, 47, 11,0)OnClick(btn, m)

И наоборот, допустим, мы определяем подпрограмму RelaxedOnClick, в которую передаются два параметра Object, и затем мы можем вызвать ее с фактическими аргументами типа Object и EventArgs:

Sub RelaxedOnClick(sender As Object, e As Object) Handles btn.Click
  MessageBox.Show("Hello World from" & btn.Text))
End Sub
Dim e As EventArgs = m
Dim s As Object = btn
RelaxedOnClick(btn,e)

В Visual Basic 9.0 привязка к делегатам ослаблена, чтобы соответствовать вызовам методов. Это означает, что если возможно вызвать функцию или подпрограмму с фактическими аргументами, которые точно соответствуют формальным параметрам и возвращаемым типам делегата, такой вызов допустим. Другими словами, привязка и определение делегатов будет подчиняться той же логике перегрузки и приведения типов, что и вызовы этого методов.

Из этого следует, что в Visual Basic 9.0 мы можем привязать подпрограмму RelaxedOnClick, в которую передаются два параметра Object, к событию Click объекта Button:

Sub RelaxedOnClick(sender As Object, e As Object) Handles btn.Click
  MessageBox.Show(("Hello World from" & btn.Text)
End Sub

В обработчиках событий редко используются оба аргумента sender и EventArgs. Обычно обработчик получает доступ к состоянию элемента управления, которому принадлежит событие, и игнорирует эти два аргумента. Для поддержки этой обычной ситуации, правила для делегатов можно ослабить настолько, что не передавать в них аргументы вообще, если при этом не возникает неопределенность. Другими словами, можно просто написать следующее:

Sub RelaxedOnClick Handles btn.Click
  MessageBox.Show("Hello World from" & btn.Text)
End Sub

Понятно, что упрощение делегатов также применяется при создании делегатов, использующих AddressOf или выражения, даже если группа методов является вызовами с поздней привязкой:

Dim F As EventHandler = AddressOf RelaxedOnClick
Dim G As New EventHandler(AddressOf btn.Click)

Заключение

Visual Basic 9.0 унифицирует доступ к данным независимо от их источника: к реляционным базам данных, XML-документам, или произвольным графам объектов, постоянно присутствующих или хранящихся в памяти. Унификация затрагивает стили, приемы, средства и модели программирования. Особо гибкий синтаксис Visual Basic упрощает добавление к языку расширений, таких, как XML-литералы и SQL-подобные выражения запросов. За счет того, что чужой синтаксис переносится из строковых констант в язык Visual Basic, удается значительно сократить прямую работу с новыми API-интерфейсами .NET при запросах, упростить работу за счет использования IntelliSense и смарт-тегов, и значительно улучшить отладку и проверку при компиляции.

Такие возможности, как вычисление типов, инициализаторы объектов и нестрогие делегаты, резко снижают избыточность кода, и количество исключений из правил, которые необходимо изучить и помнить программистам, не снижая при этом производительность приложений.

Хотя может показаться, что список новых функций в Visual Basic 9.0 длинен, мы надеемся, что рассмотренные выше темы убедят вас в том, что уместно, своевременно и правильно будет назвать Visual Basic лучшим языком программирования в мире. Мы надеемся, что разбудили ваше воображение и что вы согласитесь с тем, что впереди нас ждут ещё более замечательные вещи.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=5222