Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 

Обзор 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")

Visual Stuio сама вычисляет тип переменной

Как видите, компилятор не только не выдает ошибку о том что тип переменной не указан, но еще и правильно определяет тип, предоставляя соответствующий 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)

Оптимизатор правда немного смухлевал, но смысл, думаю, понятен smile_regular

Инициализация объектов и массивов (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 на данный момент утверждает что неявная типизация и в этом случае должна работать smile_angry. Посмотрим что будет в релизе.

Ну что ж, фича интересная, но пока не представляю где она может упростить код smile_thinking

Nullable типы

Nullable типы появились еще в .NET 2.0, но в C# 2.0 они уже были частью синтаксиса, а в VB 8.0 приходилось ограничиваться классом Nullable(Of T As Structure), но об этом мало кто знал smile_regular.

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

Что ж, радуйтесь - теперь у вас есть возможность залезть внутрь любого класса. Правда не по настоящему smile_tongue

Метод расширения - это обычный статический метод, расположеный где-нибудь в вашем коде, помеченый атрибутом 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)

Ну как? smile_teeth

Как вы и поняли, никакого волшебства здесь нет, и внутрь класса вас никто не пускает, просто как и в других случаях, компилятор 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-варажения? А без регулярного выражения? smile_devil

        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

Да да, параметры можно вообще не указывать.

Вопрос в том - нафига это надо? Мне лично не понятно. smile_angry

При компиляции 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/

С уважением,
Артем Кривокрисенко

 
     

   
   
     
  VBNet рекомендует