Обзор Visual Basic 9.0: рестайлинг
Релиз Microsoft Visual Studio 2008 (codename Orcas) не за горами, а вместе с ним мы увидем и новый Visual Basic - Visual Basic 9.0.
Какие же изменения нам сулит новая версия?
Изменений немало, из которых конечно, основное - это LinQ. Но обо всем по порядку.
Что бы хотелось отметить прежде всего - рантайм не изменился. Все нововведения которые есть в .NET Framework 3.5 и Visual Basic 9.0 - это всего лишь несколько новых сборок и новый компилятор.
Неявная типизация локальных переменных (Implicitly Typed Local Variables)
По моему мнению величайшее изобретение Visual Basic .NET - это директива Option Strict On. Требование явной типизации позволяет избежать бесчисленого количества глупых ошибок. Что же случилось в VB 9.0? Опять уходим от явной типизации? Не совсем.
В Visual Basic 9.0 в тех случаях когда компилятор может сам узнать тип переменной, его указывать не обязательно. Например:
Dim X = 10 ' Integer
Console.WriteLine(X.CompareTo(50))
Dim Text As String = "Hello world" ' String
Console.WriteLine(Text.Substring(5))
Dim Stream = New IO.FileStream("c:\1.txt", IO.FileMode.OpenOrCreate) 'FileStream
Stream.Dispose()
Dim Items = New List(Of String) ' List(Of String)
Items.Add("Hello")
Items.Add("World")
Как видите, компилятор не только не выдает ошибку о том что тип переменной не указан, но еще и правильно определяет тип, предоставляя соответствующий IntelliSense. Вполне очевидно - тип он определяет исходя из того, чем переменную инициализируют. То есть это работает только если переменная инициализируется при объявлении.
Также неявная типизация работает в циклах For:
For i = 0 To 100
Console.WriteLine(i * 20)
Next
Dim Words As New List(Of String)2
Words.Add("hello")
Words.Add("world")
For Each Word In Words
Console.WriteLine(Word.Length)
Next
В цикле For Each тип переменной выводится исходя из типа IEnumerable и IEnumerable(Of T).
Если неявная типизация вам нен нужна, эту фичу можно отключить в свойствах проекта на вкладке Compile, в настройках Visual Studio или соответствующий директивой компилятора:
Option Infer Off
По умолчанию неявная типизация включена. Неявная типизация может работать наряду с явной не мешая друг другу.
Неявная типизация работает только с локальными переменными (для полей, свойств, параметров методов необходимо указывать тип, разумеется, если включена директива Option Strict).
Влияет ли неявная типизация на работу кода? Работает ли здесь позднее связывание, которое замедляет работу?
Нет, никак не влияет - на этапе компиляции компилятор vbc вычисляет тип переменных, и в IL у всех переменных типы явно прописаны, аналогично как при явной типизации.
Я не совсем хорошо знаю IL но по-моему это то что нужно:
Dim X = 10 ' Integer
Console.WriteLine(X.CompareTo(50))
L_0001: ldc.i4.s 10
L_0003: stloc.3
L_0004: ldloca.s X
L_0006: ldc.i4.s 50
L_0008: call instance int32 [mscorlib]System.Int32::CompareTo(int32)
L_000d: call void [mscorlib]System.Console::WriteLine(int32)
Оптимизатор правда немного смухлевал, но смысл, думаю, понятен
Инициализация объектов и массивов (Object and Array Initializers)
Допустим у нас есть определенный тип:
Public Class User
Public Name As String
Public Age As Integer
Public DateBirth As DateTime
End Class
Каким образом проще всего его проинициализировать? Можно так:
Dim User As New User
With User
.Name = "Dark Brand"
.Age = 20
.DateBirth = #7/13/1987#
End With
Но в VB 9.0 есть более краткая и удобная конструкция:
Dim User As New User With {.Name = "Dark Brand", .Age = 20, .DateBirth = #7/13/1987#}
Или, как предлагают в примере в MSDN:
Dim User As New User With {.Name = "Dark Brand", _
.Age = 20, _
.DateBirth = #7/13/1987#}
Прошу обратить внимание, это не конструктор, компилятор vbc разложит это на 3 отдельные строчки.
Также можно инициализировать и массивы, хотя это вобщем-то и совсем не нововведение:
Dim Users As User() = {New User With {.Name = "Dark Brand", _
.Age = 20, _
.DateBirth = #7/13/1987#}, _
New User With {.Name = "Black Brand", _
.Age = 25, _
.DateBirth = #7/13/1982#}, _
New User With {.Name = "Light Brand", _
.Age = 30, _
.DateBirth = #7/13/1977#}}
К сожалению неявная типизация здесь не срабатывает - приходится явно указывать тип массива, хотя MSDN на данный момент утверждает что неявная типизация и в этом случае должна работать . Посмотрим что будет в релизе.
Ну что ж, фича интересная, но пока не представляю где она может упростить код
Nullable типы
Nullable типы появились еще в .NET 2.0, но в C# 2.0 они уже были частью синтаксиса, а в VB 8.0 приходилось ограничиваться классом Nullable(Of T As Structure), но об этом мало кто знал .
Dim DateStart As Nullable(Of Date)
Nullable тип ввели для тех случаев, когда структурная перменная должна иметь возможность принимать значение Nothing. Очень часто эта необходимость возникает при работе с СУБД - ведь в них есть возможность указывать NULL для любых значений.
В VB 9.0 Nullable типы тоже сделали частью синтаксиса:
Dim DateStart As Date? = Nothing
Console.WriteLine("DateStart.HasValue: {0}", DateStart.HasValue)
DateStart = DateTime.Now
Console.WriteLine("DateStart.HasValue: {0}", DateStart.HasValue)
Console.WriteLine("DateStart.Value: {0}", DateStart.Value)
Console.WriteLine("DateStart.Value.Month: {0}", DateStart.Value.Month)
Console.ReadLine()
При компиляции тип Date? превратится в Nulalble(Of Date), то есть эти две строчки аналогичны:
Dim DateStart As Nullable(Of Date)
Dim DateStart As Date?
Прошу заметить, это работает только со структурными типами. Ссылочные типы и так могут принимать значение Nothing и попытка сделать их Nullable вылется в ошибку компиляции.
Nullable-типом может быть не только локальная переменная, но и свойство, поле, параметр и т.п.
Безусловно, нововведение удобное, обязательно буду использовать.
Методы расширения (Extension Method)
Признайтесь честно - никогда не возникало мысли типа "Ох уж эти разрабочтики .NET Framework... Не могли такую важную функцию вставить в этот класс...". И приходилось писать эту важную фукнцию в отдельном модуле, и использовать ее оттуда. Потом этих важных функций собиралось много, приходилось разбираться в их ворохе... Вобщем невесело.
Что ж, радуйтесь - теперь у вас есть возможность залезть внутрь любого класса. Правда не по настоящему
Метод расширения - это обычный статический метод, расположеный где-нибудь в вашем коде, помеченый атрибутом System.Runtime.CompilerServices.Extensions, принимающий в качестве первого параметра объект с которым вы хотите работать.
Например, у класса String по непонятной причине нет метода Reverse для того чтоб изменить порядок символов на противоположный. не беда, напишем для этого функцию:
Module Extensions
''' <summary>
''' Изменяет порядок символов в строке на противоположный
''' </summary>
<Extension()> _
Public Function Reverse(ByVal sourceString As String) As String
Dim Result As String = ""
For i = sourceString.Length - 1 To 0 Step -1
Result += sourceString(i)
Next
Return Result
End Function
End Module
Способ разворота конечно не самый оптимальный :)
А теперь самое интересное - как вызвать эту функцию?
Dim Text = "Hello world!"
Dim Reverse = Text.Reverse
Console.WriteLine(Reverse)
Ну как?
Как вы и поняли, никакого волшебства здесь нет, и внутрь класса вас никто не пускает, просто как и в других случаях, компилятор vbc делает свое дело. В данном случае в IL уже идет обычный вызов статического метода:
L_0001: ldstr "Hello world!"
L_0006: stloc.1
L_0007: ldloc.1
L_0008: call string VB2005.Extensions::Reverse(string)
L_000d: stloc.0
L_000e: ldloc.0
L_000f: call void [mscorlib]System.Console::WriteLine(string)
Но согласитесь - выглядит очень эффектно!
Какие возможности это дает? Да просто огромные! Наверное читая эти строчки вы уже знаете где будете использовать эту фичу :)
Кстати, программисты Microsoft не дремлют и уже написали кучу Extensions-методов. Посмотрите, например, классы System.Linq.Queryable и System.Linq.Enumerable.
Как вам фукнция которая вычисляет среднее арефмитическое, сумму элементов в массиве (или в любом IEnumerable):
Dim Numbers As Integer() = {10, 20, 30, 40, 50, 60}
Console.WriteLine(Numbers.Average())
Console.WriteLine(Numbers.Sum())
Console.ReadLine()
Мое мнение - однозначно зочет!
Лямбда-выражения (Lambda Expressions)
Мы приближаемся к более серьезным и сложным изменениям.
Лямбда-выражения в VB .NET 9.0 имеют запутаную историю. Изначально их как бы и не было судя по докуменатции. Потом в документации они появились, но компилятор их не понимал. И в конце концов в одной из последних сборок Visual Studio 2008 (Beta 1 или Beta 2 - точно не скажу) - они заработали!
Рассмотрим несложную задачу. У нас есть список чисел, из них нужно отобрать числа которые больше 50. Эта задача очень просто решается циклом, но мы воспользуемся Extensions-методом Where. Он принимает делегат:
Public Delegate Function Func(Of T, TResult)(ByVal arg As T) As TResult
Public Shared Function Where(Of TSource)(ByVal source As System.Collections.Generic.IEnumerable(Of TSource), _
ByVal predicate As System.Func(Of TSource, Boolean)) As System.Collections.Generic.IEnumerable(Of TSource)
Если не обращать внимания на кучу типов - то Where принимает ссылку на функцию, которая принимает некоторый элемент в качестве параметра и возвращает Boolean. Если True то элемент добавляется до результирующего множества, если False - соответственно, нет.
Для начала подготовим список:
Dim Numbers = New List(Of Integer)
Dim Rnd = New Random
For i = 0 To 100
Numbers.Add(Rnd.Next(0, 100))
Next
Подготавливаем функцию которая будет проверять, подходит нам определенный элемент или нет:
Function GraterThen50(ByVal item As Integer) As Boolean
Return item > 50
End Function
Ну понятно - если больше 50 подходит, если нет - не подходит.
А теперь собственно вызов Where:
For Each Num As Integer In Numbers.Where(AddressOf GraterThen50)
Console.Write(Num.ToString + " ")
Next
Неудобство заключается в необходимости создавать целую функцию для проверки. Если у вас будет по несколько условий отбора, то создание кучи функцию будет напрягать. Вот здесь на помощь и приходят Lambda-выражения.
С Lambda-выражением код будет выглядет следующим образом:
Dim Numbers = New List(Of Integer)
Dim Rnd = New Random
For i = 0 To 100
Numbers.Add(Rnd.Next(0, 100))
Next
For Each Num As Integer In Numbers.Where(Function(item As Integer) item > 50)
Console.Write(Num.ToString + " ")
Next
Function(item As Integer) item > 50 - это и есть lambda-выражение. Фактически это фукнция которая принимает Integer и возвращает Boolean, но она объявляется прямо на месте ее использования.
На этапе компиляции это lambda-выражение компилируется в обычную функцию и с технической точки зрения работа кода не будет отличаться от работы первого варианта с отдельной функцией.
Рассмотрим еще пару вариантов использования Lambda-выражений.
Dim Min As Integer = 50
For Each Num As Integer In Numbers.Where(Function(item As Integer) item > Min)
Console.Write(Num.ToString + " ")
Next
Если бы мы продолжали использовать функцию, ситуация бы осложнилась так как в lambda-выражении используется локальная переменная - пришлось бы искать способ передать его в функцию. С Lambda-выражениями, разумеется, никакой проблемы нет.
Любознательные могут посмотреть сборку рефлектором - при компиляции создается отдельный класс, в котором находится функция и в поле хранится значение необходимой переменной.
Lambda-выражение может возвращать любые значения, не только логические
Вычисление максимального значения:
Dim MaxValue = Numbers.Aggregate(Function(max, current) CInt(IIf(current > max, current, max)))
Более сложный пример - вычисление среднего геометрического.
Dim Numbers = New List(Of Integer)
Dim Rnd = New Random
For i = 0 To 20
Numbers.Add(Rnd.Next(1, 100))
Next
Dim Avg = Numbers.Aggregate(1, _
Function(total As Double, current As Integer) total * current, _
Function(total As Double) total ^ (1 / Numbers.Count))
Console.WriteLine(Avg)
Наверное я утомил вас этой арихметикой. Вот более красивый и жызненный пример - "разворот" всех слов в строке. Сколько строчек вам понадобилось бы без lamba-варажения? А без регулярного выражения?
Dim Text As String = "hello world"
Dim Regex As New System.Text.RegularExpressions.Regex("\w+")
Console.WriteLine(Regex.Replace(Text, _
Function(match As System.Text.RegularExpressions.Match) match.Value.Reverse))
Вобщем мое мнение - очень интересная и полезная фича. Но нужно потратить некоторое время что ознакомиться и привыкнуть к ней.
Разумеется, опять таки - никакого волшебства нет - как я и говорил выше - компилятор vbc "разворачивает" lambda-выражение в самую обычную функцию.
Кстати, в C# lambda-выражения имеют более краткий синтаксис:
locals = customers.Where(c => c.ZipCode == 91822);
Ну что ж, спасибо за то что есть.
Расслабленые делегаты (Relaxed delegates)
Все о чем я написал выше я считаю однозначно полезными нововведения.
А вот расслабленые делегаты - по моему мнению - штука абсолютно бесполезная, не понимаю зачем ее выстрадали.
Рассмотрим обычный обрабочтик событий:
Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles Me.MouseDown
End Sub
В VB 2005 обработчик событий должен был иметь точно такую же сигнатуру как и само событие. То есть если мы заменим MouseEventArgs на Object, будет ошибка компиляции, хотя типа MouseEventArgs без проблем приводится к Object.
В VB 9.0 этого ограничения нет, т.е. указывать можно любые типы лишь бы они друг к другу приводились, т.е. это вполне рабочий обрабочтик:
Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As EventArgs) Handles Me.MouseDown
End Sub
И это:
Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As Object) Handles Me.MouseDown
End Sub
И это:
Private Sub Form1_MouseDown() Handles Me.MouseDown
End Sub
Да да, параметры можно вообще не указывать.
Вопрос в том - нафига это надо? Мне лично не понятно.
При компиляции vbc-компилятор все-таки создает "нормальное" событие из которого делает вызов этого "расслабленого делегата". Ну вобщем нормально.
Другие нововведения
В данном обзоре я рассмотрел ряд нововведений VB 9.0.
Но есть еще несколько очень "вкусных" нововведений которые я не рассмотрел, а именно:
- Анонимные типы (Anonymous Types)
- "Глубокая" поддержка XML (Deep XML Support)
- И, разумеется, очень нашумевший легендарный LinQ
В одном из следующих постов в блоге я постараюсь осветить эти темы.
Заключение
Почему рестайлинг? Все просто. Под капотом у нас остался тот же самый рантайм от .NET Framework 2.0.Все что мы видим в VB 9.0 - это новые возможности компилятора vbc и новые сборки .NET Framework 3.5.
Плохо это или хорошо? Я ничего плохого не вижу - судя по всему рантайм достаточно хорош, улучшать уже некуда, а новые возможности без пробелм реализуются на его основе.
Практически все изменения направлены на упрощения работы и уменьшение количества кода. Что не может не радовать.
Ну что ж, ждем релиза, а кому не хочется ждать ждать - качайте Visual Studio 2008 Beta 2 (осторожно, на Windows Server 2008 Beta 3 немного капризничает).
Ссылки
Если у вас есть какие-то вопросы по теме, можете со мной связаться:
ICQ: 4407797
E-mail: artyom@webreflection.ru
Мой блог: http://darkbrand.spaces.live.com/
С уважением,
Артем Кривокрисенко