С момента появления в 1991 году язык Visual Basic всегда был феноменально производительным инструментом для создания приложений. Почти 20 лет спустя он обеспечивает простой доступ к Microsoft .NET Framework, позволяя разработчикам писать приложения для самых разных применений — от настольных компьютеров и мобильных телефонов до платформ вычислений в облаке.
В апреле Microsoft начинает поставки Visual Studio 2010, который включает в себя Visual Basic 10 (иногда называемый VB 2010 или VB10). Этот выпуск — на данный момент самый мощный — содержит многочисленные средства, ускоряющие разработку и позволяющие писать меньше кода. В этой статье есть все для того, чтобы взять с места в карьер с Visual Basic в Visual Studio 2010.
Совместная эволюция
В прошлом Visual Basic и C# разрабатывались отдельными группами, что часто приводило к появлению каких-либо языковых средств в одном языке раньше, чем в другом. Например, в C# были автоматически реализуемые свойства (auto-implemented properties) и инициализаторы набора, которые отсутствовали в Visual Basic, а в Visual Basic давно были такие возможности, как позднее связывание и необязательные параметры, которых не было в C#. Но, как только некое языковое средство появлялось в одном из этих языков, многие заказчики начинали просить добавить эквивалентную возможность и в другой язык.
В итоге в Microsoft решили объединить группы Visual Basic и C#, приняв на вооружение стратегию совместной эволюции. Цель очевидна — совместное развитие языков. Когда в одном из языков вводится новая значимая функциональность, она должна появиться и в другом. Это не значит, что каждая функция должна присутствовать в обоих языках и работать совершенно одинаково; на самом деле у каждого языка свой история, свой дух — все это важно сохранять. Таким образом, совместная эволюция подразумевает, что любая задача, которую нетрудно решить на одном из языков, должна так же легко решаться и на другом.
В .NET Framework 4 в обоих языках — Visual Basic и C# — сделаны гигантские шаги в этом направлении и в каждый язык добавлен ряд возможностей, уже присутствовавших в другом. Но совместная эволюция не завязана на одни прошлые достижения — это еще и стратегия будущих инноваций в этих языках. Ради этого в .NET Framework 4 введены новые мощные средства вроде Dynamic Language Runtime, Embed Interop Types и обобщенной вариантности (generic variance), поддержка которых была реализована в обоих языках одновременно, что позволит разработчикам на Visual Basic и C# задействовать все преимущества .NET Framework 4.
Новые средства в Visual Basic 2010
Такие средства рассчитаны на то, чтобы вы могли делать больше, но при этом писать меньше строк кода. Мы (группа проектирования Visual Basic) изучали места, в которых разработчикам приходилось часто писать много утомительного стереотипного кода, и анализировали способы, которые позволили бы возложить эту работу на компилятор. Это картина в целом; теперь подробнее рассмотрим некоторые возможности.
Неявное продолжение строки
Visual Basic — язык, ориентированный на строки, в котором используется четкий синтаксис, сходный с синтаксисом английского языка для улучшения читаемости кода. Но это часто приводит к тому, что строка вылезает за предел в 80 знаков на строку, вынуждая программистов прокручивать их туда-сюда. Вы можете использовать знак подчеркивания, сообщая компилятору, что следующая строка является частью текущей (т. е. несколько физических строк интерпретируются как единая логическая строка). Однако постоянный ввод этих знаков подчеркивания всегда был раздражающим фактором, и в течение долгих лет самым популярным запросом была просьба сделать так, чтобы компилятор «сам распознавал продолжение строки».
И вот в Visual Basic 2010 компилятор этому научился. Теперь он знает, какие лексемы (например, запятые, скобки и операторы) встречаются справа до символа продолжения строки, и сам вставляет этот символ, так что разработчикам больше не нужно думать об этом. Скажем, завершение выражения Visual Basic запятой не допускается; компилятору это известно, поэтому, обнаруживая поток лексем вроде {запятая, enter}, он логически распознает присутствие символа продолжения строки, как показано в примере на рис. 1.
Рис. 1 Логическое распознавание продолжения строки
<Extension()>
Function FilterByCountry(
ByVal customers As IEnumerable(Of Customer),
ByVal country As String) As IEnumerable(Of Customer)
Dim query =
From c In customers
Where c.Country = country
Select <Customer>
<%=
c.Name &
"," &
c.Country
%>
</Customer>
Return query
End Function
В Visual Basic 2008 в коде на рис. 1 потребовались бы девять знаков подчеркивания. Однако теперь в каждом из этих случаев компилятор сам определяет, когда нужен знак подчеркивания, и разрешает опускать его:
- после атрибута <Extension()>;
- после открывающей скобки в объявлении метода;
- после запятой за первым параметром;
- до закрывающей скобки в объявлении метода;
- после знака «равно»;
- после комбинации знаков «<%=»;
- после каждого амперсанда (&) в XML-литерале;
- до комбинации знаков «%>».
Эта новая возможность компилятора особенно удобна в показанном примере сигнатуры метода, длина которой вышла бы далеко за пределы 80 знаков, если бы каждая ее часть была на одной и той же строке. На рис. 2 перечислены все комбинации лексем и их мест расположения, при которых символ продолжения строки подставляется неявно.
Рис. 2 Случаи, в которых подразумевается неявное использование символов продолжения строк
Лексема | До | После |
, (запятая), . (точка), > (атрибуты), ( { (открывающие скобки), <%= (начало встроенного выражения, например XML-литералы)) | | X |
), }, , ] (закрывающие скобки), %> (конец встроенного выражения) | X | |
Все ключевые слова LINQ: Aggregate, Distinct, From, Group By, Group Join, Join, Let, Order By, Select, Skip, Skip While, Take, Take While, Where, In, Into, On, Ascending, Descending | X | X |
Операторы: + , - , * , / , \ , ^ , >> , << , Mod, & , += , -= , *= , /= , \= , ^= , >>= , <<= , &= , < , <= , > , >= , <> , Is, IsNot, Like, And, Or, Xor, AndAlso, OrElse | | X |
With (в инициализаторе объекта) | | X |
Как видите, более чем в 60 местах язык больше не требует знаков подчеркивания. (Фактически ни один из примеров кода в этой статье не требует присутствия символов продолжения строк.) Конечно, вы можете по-прежнему ими пользоваться, и код, написанный на предыдущих версиях Visual Basic, будет нормально компилироваться.
Лямбды выражений
Термин «лямбда» поначалу звучит устрашающе, но лямбда — это просто функция, определенная внутри другой функции. В Visual Basic 2008 были введены лямбда-выражения (lambda expressions), поддерживаемые ключевым словом Function:
Dim customers As Customer() = ...
Array.FindAll(customers, Function(c) c.Country = "Canada")
Лямбда-выражения — отличный способ компактного выражения локальной логики без распределения по нескольким методам. Например, вот как выглядел бы предыдущий код в Visual Basic 2005 (где нет поддержки лямбда-выражений):
Dim query = Array.FindAll(customers, AddressOf Filter)
...
Function Filter(ByVal c As customer) As Boolean
Return c.Country = "Canada"
End Function
Увы, лямбда-выражения в Visual Basic 2008 требовали, чтобы выражения возвращали какое-то значение, поэтому вот такой код:
Array.ForEach(customers, Function(c) Console.WriteLine(c.Country))
привел бы к ошибке компиляции:
'Compile error: "Expression does not produce a value."
Console.WriteLine является процедурой типа Sub (void в C#), а значит, не возвращает значение, из-за чего компилятор сообщает об ошибке. Чтобы устранить эту проблему, в Visual Basic 2010 введена поддержка лямбд выражений (statement lambdas), которые являются лямбдами, содержащими одно или более выражений:
Array.ForEach(customers, Sub(c) Console.WriteLine(c.Country))
Так как Console.WriteLine не возвращает значение, мы можем просто создать лямбду Sub вместо лямбды Function. Вот другой пример, где используется несколько выражений:
Array.ForEach(customers, Sub(c)
Console.WriteLine("Country Name:")
Console.WriteLine(c.Country)
End Sub)
При выполнении этот код выводит две строки для каждого клиента. Также обратите внимание: если вы задержите курсор мыши над буквой c в процессе кодирования, то увидите, что компилятор логически определил тип как Customer (можно явно задать тип как c As Customer). Еще одно отличное применение лямбд выражений — динамическое подключение обработчиков событий:
AddHandler b.Click, Sub(sender As Object, e As EventArgs)
MsgBox("Button Clicked")
'insert more complex logic here
End Sub
И по сути, вы можете комбинировать лямбды выражений с языковым средством, введенным в Visual Basic 2008: нестрогими делегатами (relaxed delegates). (Делегаты — безопасные в многопоточной среде указатели на функции — позволяют выполнять одновременно несколько функций.) Эта комбинация позволяет еще больше упростить сигнатуру:
AddHandler b.Click, Sub()
MsgBox("Button Clicked")
'insert more complex logic here
End Sub
Нестрогий делегат позволяет полностью опускать параметры в обработчике событий — хорошая возможность, учитывая, что зачастую они вообще не используются и только создают «визуальный шум».
Кроме одно- и многострочных лямбд Sub, которые вы уже видели, Visual Basic 2010 поддерживает многострочные лямбды Function:
Dim query = customers.Where(Function(c)
'Return only customers that have not been saved
'insert more complex logic here
Return c.ID = -1
End Function)
Другой интересный аспект лямбд выражений — то, как они пересекаются с анонимными делегатами, введенными в Visual Basic 2008. Их часто путают с анонимными методами в C#, хотя с технической точки зрения это вовсе не одно и то же. Анонимные делегаты создаются, когда компилятор Visual Basic логически распознает тип делегата, исходя из сигнатуры метода лямбды:
Dim method = Function(product As String)
If product = "Paper" Then
Return 4.5 'units in stock
Else
Return 10 '10 of everything else
End If
End Function
MsgBox(method("Paper"))
Если вы запустите этот код, в окне сообщения появится значение «4.5». Кроме того, если вы задержите курсор мыши над словом method, то увидите текст Dim method As <Function(String) As Double>. Поскольку мы не указали тип делегата, компилятор сгенерирует таковой автоматически:
Delegate Function $compilerGeneratedName$(product As String) As Double
Этот делегат называется анонимным, потому что появляется не в исходном коде, а только в сгенерированном компилятором. Заметьте, что компилятор логически определил тип возвращаемого значения как Double, когда в исходном коде фактически не было блока As, который сообщал бы тип значения, возвращаемый лямбдой. Компилятор просматривает все выражения return внутри лямбды и видит типы Double (4.5) и Integer (10):
'Notice the "As Single"
Dim method = Function(product As String) As Single
If product = "Paper" Then
Return 4.5 'units in stock
Else
Return 10 '10 of everything else
End If
End Function
Компилятор запускает свой алгоритм поиска доминантного типа и определяет, что значение «10» можно безопасно преобразовать в тип Double, но преобразовать «4.5» в Integer нельзя, поэтому выбирает Double.
Вы можете явным образом контролировать тип возвращаемого значения, и тогда компилятор не станет пытаться логически определять его тип. Как правило, вместо того чтобы полагаться на распознавание компилятором типа делегата, лямбда присваивается переменной с явным типом делегата:
Dim method As Func(Of String, Single) =
Function(product)
If product = "Paper" Then
Return 4.5 'units in stock
Else
Return 10 '10 of everything else
End If
End Function
Поскольку целевой тип был предоставлен явным образом, объявлять As String или As Single не требуется; компилятор может логически определить их на основе типа делегата в левой части выражения. В этом случае, если вы задержите курсор мыши над product, то увидите, что его тип логически определен как String. Указывать As Single больше нет нужды, поскольку тип делегата уже предоставил эту информацию. В предыдущем примере сигнатура делегата Func (поддерживаемого .NET Framework) выглядит так:
Delegate Function Func(Of T, R)(ByVal param As T) As R
но с одним небольшим исключением, о котором я расскажу в разделе по обобщенной вариантности.
Автоматически реализуемые свойства
В Visual Basic свойства являются членами класса, которые вы используете, чтобы предоставить внешнему миру доступ к состоянию объекта. Типичное объявление свойства выглядит так:
Private _Country As String
Property Country As String
Get
Return _Country
End Get
Set(ByVal value As String)
_Country = value
End Set
End Property
Здесь приходится писать 10 строк кода, чтобы выразить на самом деле крайне простую концепцию. Учитывая, что в типичных объектах обычно содержатся десятки свойств, вы вынуждены включать в определения классов массу стереотипного кода. Чтобы облегчить подобные задачи, в Visual Basic 2010 введены автоматически реализуемые свойства (auto-implemented properties), которые позволяют определять простое свойство всего одной строкой кода:
Property Country As String
В этом случае компилятор автоматически генерирует аксессоры Getter и Setter, а также поддерживающие поля (backing fields). Имя поддерживающего поля всегда начинается со знака подчеркивания, за которым следует имя свойства (в нашем примере _Country). Это соглашение по именованию гарантирует совместимость на уровне двоичной сериализации на случай, если автоматически реализуемое свойство вдруг придется переделать в обычное. Если имя поддерживающего поля одно и то же, двоичная сериализация будет работать.
Одно из впечатляющих качеств автоматически реализуемых свойств — возможность указывать инициализаторы, которые устанавливают значение свойства по умолчанию при выполнении конструктора. Например, в случае классов сущностей очень часто нужно присвоить основному ключу значение вроде –1, указывающее, что он находится в несохраненном состоянии. Вот как мог бы выглядеть соответствующий код:
Property ID As Integer = -1
При выполнении конструктора поддерживающее поле (_ID) автоматически получит значение –1. Синтаксис инициализатора работает и для ссылочных типов:
Property OrderList As List(Of Order) = New List(Of Order)
Эта строка кода выглядит в несвойственном Visual Basic стиле; в ней приходится дважды вводить имя типа. Хорошая новость в том, что теперь есть более краткий синтаксис в стиле Visual Basic:
Property OrderList As New List(Of Order)
Вы можете сочетать его даже с инициализаторами объекта для настройки дополнительных свойств:
Property OrderList As New List(Of Order) With {.Capacity = 100}
Очевидно, что для более сложных свойств все равно понадобится расширенный синтаксис. Для активации предыдущего фрагмента с кодом свойства вы по-прежнему можете использовать Property{Tab}. В качестве альтернативы после набора первой строки свойства можно просто ввести Get{Enter}, и IDE сгенерирует свойство в старом стиле:
Property Name As String
Get
End Get
Set(ByVal value As String)
End Set
End Property
Многие отмечают, что новый синтаксис свойств почти идентичен синтаксису открытых полей.И почему бы тогда не использовать открытые поля вместо свойств? Ну, на то есть несколько причин.
- Основная часть .NET-инфраструктуры связывания с данными работает со свойствами, а не полями.
- Интерфейс не может требовать наличия поля — такое возможно только для свойства.
- Свойства обладают большей гибкостью, если впоследствии вам понадобится изменить бизнес-правила. Например, кто-то вводит правило, что телефонный номер должен состоять из 10 цифр. Если назначить его открытому полю, вы не сможете выполнять такую проверку. Переделка открытого поля в свойство является разрушающим изменением в таких случаях, как выполнение двоичной сериализации и использование отражения.
Инициализаторы набора
Распространенная в .NET практика — создание экземпляра набора с последующим его заполнением вызовом метода Add для каждого элемента:
Dim digits As New List(Of Integer)
digits.Add(0)
digits.Add(1)
digits.Add(2)
digits.Add(3)
digits.Add(4)
digits.Add(5)
digits.Add(6)
digits.Add(7)
digits.Add(8)
digits.Add(9)
Но в результате для крайне простой концепции мы получаем слишком большие издержки из-за синтаксиса. В Visual Basic 2010 введена поддержка инициализаторов набора, упрощающих создание экземпляров наборов. При использовании такого кода:
Dim digits = New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
компилятор сам генерирует все вызовы метода Add. Вы также можете использовать эту функциональность с помощью синтаксиса As New:
Dim digits As New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
Заметьте, что группа Visual Basic всегда рекомендует использовать второй синтаксис (As New), так как он делает код устойчивым к изменению настройки Option Infer.
Вы можете использовать инициализаторы набора применительно к любому типу, который отвечает следующим требованиям.
- Набор можно перебирать с помощью оператора For Each, т. е. он реализует IEnumerable. (Более подробное и точное описание типа-набора см. в разделе 10.9.3 спецификации языка Visual Basic по ссылке msdn.microsoft.com/library/aa711986(VS.71).aspx.)
- У него есть доступный (необязательно открытый) конструктор без параметров.
- У него есть доступный (необязательно открытый) метод экземпляра или расширения с именем Add.
Это означает, что вы можете использовать инициализаторы набора и с более сложными типами, такими как словари:
Dim lookupTable As New Dictionary(Of Integer, String) From
{{1, "One"},
{2, "Two"},
{3, "Three"},
{4, "Four"}}
(Заметьте:хотя это выражение занимает пять строк, в нем нет знаков подчеркивания.) В данном случае компилятор сгенерирует код, эквивалентный старому инициализирующему словарь коду:
Dim lookupTable As New Dictionary(Of Integer, String)
lookupTable.Add(1, "One")
lookupTable.Add(2, "Two")
lookupTable.Add(3, "Three")
lookupTable.Add(4, "Four")
Компилятор вызывает метод Add с двумя параметрами, а не с одним. Он распознает, что делать нужно именно так, поскольку значения, переданные инициализатору набора, были заключены во вложенные скобки наподобие {{1, “One”}, {2, “Two”}, …}. Для каждого набора вложенных скобок компилятор пытается передать соответствующие параметры в совместимый метод Add.
Вы также можете предоставить собственную реализацию Add, используя метод расширения:
<Extension()>
Sub Add(ByVal source As IList(Of Customer),
ByVal id As Integer,
ByVal name As String,
ByVal city As String)
source.Add(New Customer With
{
.ID = id,
.Name = name,
.City = city
})
End Sub
(И вновь обратите внимание на отсутствие так надоедавших ранее знаков подчеркивания!) Этот метод расширяет любой тип, реализующий IList(Of Customer), а затем позволяет использовать новый синтаксис инициализатора набора:
Dim list = New List(Of Customer) From
{
{1, "Jon", "Redmond"},
{2, "Bob", "Seattle"},
{3, "Sally", "Toronto"}
}
Это приводит к добавлению к списку трех клиентов. Инициализаторы наборов можно использовать в сочетании с автоматически реализуемыми свойствами:
Property States As New List(Of String) From {"AL", "AK", "AR", "AZ", ...}
Литералы массивов
В Visual Basic 2010 также внесены существенные усовершенствования в работу с массивами. Рассмотрим следующий код (который нормально работает в более старых версиях):
Dim numbers As Integer() = New Integer() {1, 2, 3, 4, 5}
Видно, что все элементы массива имеют тип Integer, поэтому дважды указывать тип Integer в этой строке явно излишне. Литералы массива позволяют создавать массив, помещая все его элементы в скобки, а тип автоматически распознается компилятором:
Dim numbers = {1, 2, 3, 4, 5}
Типом numbers является не Object, а Integer() (если флаг Option Infer включен), потому что литерал массива теперь может быть самостоятельным и иметь собственный тип. Возьмем более сложный случай:
Dim numbers = {1, 2, 3, 4, 5.555}
Здесь тип numbers будет логически определен как Double(). Компилятор определяет тип, изучая каждый элемент массива и вычисляя доминантный тип (по тому же алгоритму, который обсуждался ранее при рассмотрении лямбд выражений). Но что будет, если доминантного типа нет, как в следующем коде:
Dim numbers = {1, 2, 3, 4, "5"}
В этом случае преобразование Integer в String было бы сужающим (т. е. потенциально могло бы повлечь потерю данных в период выполнения), но и преобразование String в Integer тоже оказалось бы сужающим. Поэтому единственный безопасный тип — Object() (компилятор выдаст ошибку, если включен флаг Option Strict).
Литералы массива можно вкладывать для формирования многомерных или нерегулярных (jagged) массивов:
'2-dimensional array
Dim matrix = {{1, 0}, {0, 1}}
'jagged array - the parentheses force evaluation of the inner array first
Dim jagged = { ({1, 0}), ({0, 1}) }
Исполняющая среда динамических языков
Хотя с технической точки зрения Visual Basic в своей основе является статическим языком, в нем всегда были очень мощные динамические средства, например позднее связывание (late binding). Visual Studio 2010 поставляется с новой платформой под названием Dynamic Language Runtime (DLR), которая упрощает создание динамических языков и взаимодействие между ними. Visual Basic 2010 полностью поддерживает DLR, позволяя использовать библиотеки и инфраструктуры, написанные на таких языках, как IronPython и IronRuby.
Самое интересное, что эта поддержка не потребовала никаких изменений в синтаксисе (фактически в компиляторе не было модифицировано ни одной строки кода). Разработчики могут по-прежнему, как и в предыдущих версиях Visual Basic, определять операции с поздним связыванием. Изменения внесены в исполняющую среду Visual Basic Runtime (Microsoft.VisualBasic.dll), которая теперь распознает интерфейс IDynamicMetaObjectProvider, предоставляемый DLR. Если объект реализует этот интерфейс, Visual Basic Runtime сконструирует DLR CallSite и позволит этому объекту и предоставляющему его языку object ввести в операцию собственную семантику.
Например, стандартные библиотеки Python (Python Standard Libraries) содержат файл random.py с методом shuffle, который можно использовать для случайного переупорядочения элементов в массиве. Вызвать его несложно:
Dim python As ScriptRuntime = Python.CreateRuntime()
Dim random As Object = python.UseFile("random.py")
Dim items = {1, 2, 3, 4, 5, 6, 7}
random.shuffle(items)
В период выполнения Visual Basic обнаруживает, что объект реализует IDynamicMetaObjectProvider, и передает управление DLR, которая взаимодействует с Python и выполняет метод (передавая массив, определенный в Visual Basic как аргумент метода).
Это пример вызова API с поддержкой DLR, но разработчики могут создавать свои API, использующие DLR. Самое главное при этом — реализовать интерфейс IDynamicMetaObjectProvider, и тогда компиляторы Visual Basic и C# увидят, что объект имеет особую динамическую семантику. Вместо реализации интерфейса вручную проще наследовать от класса System.Dynamic.DynamicObject (который уже реализует этот интерфейс) и переопределить пару методов. На рис. 3 показан полный пример создания собственного динамического объекта и его вызова с использованием обычного для Visual Basic механизма позднего связывания. (Подробнее о работе с динамическими объектами см. отличную статью Дуга Ротхауса (Doug Rothaus) по ссылке blogs.msdn.com/vbteam/archive/2010/01/20/fun-with-dynamic-objects-doug-rothaus.aspx.)
Рис.3 Создание собственного динамического объекта и его вызов из Visual Basic через механизм позднего связывания
Imports System.Dynamic
Module Module1
Sub Main()
Dim p As Object = New PropertyBag
p.One = 1
p.Two = 2
p.Three = 3
Console.WriteLine(p.One)
Console.WriteLine(p.Two)
Console.WriteLine(p.Three)
End Sub
Class PropertyBag : Inherits DynamicObject
Private values As New Dictionary(Of String, Integer)
Public Overrides Function TrySetMember(
ByVal binder As SetMemberBinder,
ByVal value As Object) As Boolean
values(binder.Name) = value
Return True
End Function
Public Overrides Function TryGetMember(
ByVal binder As GetMemberBinder,
ByRef result As Object) As Boolean
Return values.TryGetValue(binder.Name, result)
End Function
End Class
End Module
Обобщенная вариантность
Эта функциональность, название которой поначалу кажется весьма заумным (с ее терминами «ковариантность» и «контравариантность»), на самом деле довольно проста. Если у вас есть объект типа IEnumerable(Of Apple) и вы хотите присвоить его IEnumerable(Of Fruit), то эта операция должна быть допустимой, так как каждый объект Apple (яблоко) наследует от Fruit (фрукт). Увы, до Visual Basic 2010 обобщенная вариантность не поддерживалась компилятором, хотя общеязыковая исполняющая среда (Common Language Runtime, CLR) полностью поддерживала такую возможность.
Рассмотрим пример, показанный на рис. 4. В Visual Basic 2008 код, показанный на рис. 4, вызвал бы ошибку компиляции (или исключение в период выполнения при отключенном флаге Option Strict) в строке Dim enabledOnly. Для обхода проблемы можно было бы вызвать метод расширения .Cast, как показано ниже:
'Old way, the call to Cast(Of Control) is no longer necessary in VB 2010
Dim enabledOnly = FilterEnabledOnly(buttons.Cast(Of Control))
Необходимости в этом больше нет, так как в Visual Basic 2010 интерфейс IEnumerable помечен как ковариантный с использованием модификатора Out:
Interface IEnumerable(Of Out T)
...
End Interface
Рис.4 Пример обобщенной вариантности
Option Strict On
Public Class Form1
Sub Form1_Load() Handles MyBase.Load
Dim buttons As New List(Of Button) From
{
New Button With
{
.Name = "btnOk",
.Enabled = True
},
New Button With
{
.Name = "btnCancel",
.Enabled = False
}
}
Dim enabledOnly = FilterEnabledOnly(buttons)
End Sub
Function FilterEnabledOnly(
ByVal controls As IEnumerable(Of Control)
) As IEnumerable(Of Control)
Return From c In controls
Where c.Enabled = True
End Function
End Class
Это означает, что обобщенный параметр T теперь является вариантным (т. е. работает с иерархией наследования) и компилятор обеспечит, что этот параметр будет использоваться только в тех позициях, где тип возвращается из интерфейса. Обобщенные параметры могут быть и контравариантными, т. е. использоваться только в тех позициях, где тип принимается. На деле тип может быть и тем, и другим. Например, делегат Func, о котором мы уже говорили, имеет как контравариантные параметры (входные), так и ковариантный (для типа возвращаемого значения):
Delegate Function Func(Of In T, Out R)(ByVal param As T) As R
Модификаторы In и Out можно применять в своих интерфейсах и делегатах. Многие часто используемые интерфейсы и делегаты в .NET Framework 4 уже помечены как вариантные, например все делегаты Action/Func, интерфейсы IEnumerable(Of T), IComparer(Of T) и IQueryable(Of T), а также ряд других.
Самое большое преимущество обобщенной вариантности — об этой функциональности не нужно беспокоиться самому:если она выполняет свою работу, вы никогда этого не заметите. Ситуации, в которых раньше возникали ошибки компиляции или требовался вызов .Cast(Of T), не должны создавать проблем в Visual Basic 2010.
Более совершенные необязательные параметры
Необязательные параметры очень удобны для создания более гибких методов и избавляют от засорения класса бесчисленными перегруженными версиями какого-либо метода. Одно из ограничений в прошлом — необязательные параметры не могли быть равны null (или любым типом невстроенной структуры). Visual Basic 2010 теперь позволяет определять необязательные параметры любого значимого типа:
Sub DisplayOrder(ByVal customer As Customer,
ByVal orderID As Integer,
Optional ByVal units As Integer? = 0,
Optional ByVal backgroundColor As Color = Nothing)
End Sub
В этом случае units имеет тип Nullable(Of Integer), а backgroundColor является типом невстроенной структуры, но их все равно можно использовать в качестве необязательных параметров. Visual Basic 2010 также обеспечивает улучшенную поддержку необязательных параметров, которые являются обобщенными.
Embed Interop Types
В приложениях, использующих COM Interop, самую большую головную боль вызывает работа с Primary Interop Assemblies (PIA). PIA — это .NET-сборка, выступающая в качестве оболочки COM-компонента, которую можно вызывать в период выполнения (Runtime Callable Wrapper, RCW) и у которой есть уникальный GUID. .NET-сборки взаимодействуют с PIA, а та потом выполняет любые необходимые операции маршалинга для передачи данных между COM и .NET.
К сожалению, PIA могут усложнить развертывание, так как они являются дополнительными DLL, которые нужно устанавливать на машинах конечных пользователей. Они также вызывают проблемы с контролем версий, например, если вы хотите, чтобы приложение могло работать и с Excel 2003, и с Excel 2007, вам придется развернуть вместе со своим приложением обе PIA.
Механизм Embed Interop Types встраивает прямо в приложение только те типы и члены из PIA, которые абсолютно необходимы, и тем самым избавляет от развертывания PIA на компьютерах конечных пользователей.
Чтобы включить этот механизм в существующем проекте (для новых ссылок он действует по умолчанию), выберите ссылку в Solution Explorer и в окне свойств установите Embed Interop Types в true (рис. 5). Или, если вы компилируете в командной строке, укажите ключ /l (или /link) вместо /r и /reference.
Рис.5 Включение поддержки Embed Interop Types в Solution Explorer
После включения этого механизма у приложения больше не будет зависимости от PIA. По сути, если вы откроете сборку в Reflector или ildasm, то заметите, что ссылки на PIA вообще нет.
Ориентация на несколько версий инфраструктуры
Самое впечатляющее во всех средствах Visual Basic 2010 заключается в том, что вы можете использовать их даже в проектах, ориентированных на .NET Framework версий 2.0–3.5. Это означает, что неявное продолжение строки, литералы массивов, инициализаторы наборов, лямбды выражений, автоматически реализуемые свойства и прочее будут работать в существующих проектах без переориентации на .NET Framework 4.
Одно исключение — Embed Interop Types, который зависит от типов, присутствующих только в .NET Framework 4; в итоге этим механизмом не удастся воспользоваться при ориентации программы на .NET Framework версий 2.0–3.5. Кроме того, типы, помеченные как вариантные, поддерживаются лишь в .NET Framework 4, поэтому в предыдущем примере вам придется по-прежнему вызывать .Cast(Of T), если ваша программа рассчитана на версии 2.0–3.5. Однако в этом случае вы можете создать собственные вариантные типы (используя модификаторы In и/или Out).
Чтобы сменить текущую целевую инфраструктуру для приложения, дважды щелкните My Project, откройте вкладку Compile, щелкните Advanced Compile Options, а затем выберите нужное из поля с раскрывающимся списком внизу.
Но при компиляции из командной строки вы не найдете соответствующего ключа. Вместо этого компилятор анализирует, какие сборки предоставляют определение System.Object (обычно mscorlib) и на какую инфраструктуру они рассчитаны, а затем просто «ставит штамп» в вашей выходной сборке. (Такой же механизм используется компилятором при создании сборок для Silverlight.) При работе из IDE все это происходит прозрачно, так что это не та проблема, о которой вам стоит беспокоиться.
Заключение
Как видите, в Visual Basic 2010 много мощных средств, ускоряющих вашу работу и в большей мере перекладывающих ее на компилятор. В этой статье я рассмотрел лишь языковые средства, но в Visual Basic 2010 IDE есть уйма других весьма значимых усовершенствований. Вот лишь некоторые из них:
- поддержка Navigate To;
- выделение ссылок;
- генерация в зависимости от использования (Generate From Usage);
- более совершенная IntelliSense;
- поддержка конфигураций с несколькими мониторами;
- масштабирование.
Группа Visual Basic была бы очень рада услышать от вас, что еще в Visual Basic нуждается в дальнейшем усовершенствовании, поэтому присылайте нам свои комментарии и вопросы на Microsoft Connect. Чтобы узнать больше о языковых средствах и функциональности IDE, читайте и смотрите материалы на msdn.com/vbasic, в том числе статьи, примеры и видеоролики из серии практических советов. И конечно же, лучший способ освоения — начать работу с продуктом, так что пора установить его и опробовать в деле.