Co je nového ve Visual Basicu 9
Visual Basic 9 je nová verze jazyka Visual Basic, která přichází spolu s .NET frameworkem 3.5 a Visual Studiem 2008. Obsahuje některá vylepšení, rozšíření a opravy chyb. Na ty se v následujícím článku podíváme podrobněji. Tento článek je postaven na verzi Beta 2, takže je možné, že ve finále se ještě něco málo změní, ale myslím si, že moc toho nebude. Nabízí se zde samozřejmě srovnání s C#. V C# ale programuji jen výjimečně, takže nemohu s jistotou říci, co v něm bylo už ve verzi 2.0 a co je nové ve verzi 3.0. Pravdou je, že VB tým je za C# vždy o něco pozadu, a ve VB tak zůstává několik věcí, které nejsou dotažené k úplné dokonalosti. Nadruhou stranu s novou verzí se VB teamu podařilo udělat velký kus práce a některé věci, které ani C# nemá.
Přehled
Podle [1] došlo k následujícím změnám ve specifikaci jazyka:
- Důležité změny
- Méně důležité změny
Dále jsou zde změny o kterých se specifikace [1] nevím proč nezmiňuje
Já sám bych ještě rád upozornil na další změny, které se přímo netýkají jazyka, ale především jeho integrace do Visual Studia.
- Intellisense pro klíčová slova
- Opravené generování XML dokumentace
- Multitargeting
Představte si třídu, k jejímuž zdrojovému kódu nemáte přístup a potřebujete ji rozšířit o nějakou funkčnost. Například takový String(který je navíc NotInheritable) a určitě u něj strašně postrádáte metodu ToCyrilic(), která by všechny znaky latinky převedla na jejich ekvivalenty v azbuce. Pokud byste takovou metodu potřebovali ve VB8, nezbývá vám než založit nějaký modul, třeba StringToolsa do něj metodu implementovat a pak ji volat nějak jako cyr$ = StringTools.ToCyrilic(lat$). Vám by se přitom líbil intuitivnější způsob - cyr$ = lat$.ToCyrilic().
A to je přesně to co extension methods - rozšiřující metody dělají.
Vezměte svoji metodu ToCyrilica označte ji atributem System.Runtime.CompilerServices.ExtensionAttribute. To vám poté umožní syntaktickou zkratku - volat metodu jako by byla členem třídy String. A to všude tam, kde bude modul StringTools"in scope" (v rozsahu platnosti). To znamená, že pokud tento modul budete mít v jiném namespacu než, kde chcete metodu použít (například v samostatném DLL), stačí když importujete namespace, ve kterém je modu obsažen. Můžete ale importovat i modul samotný.
Rozšiřující metody lze ve VB deklarovat jen na úrovni modulu. V C# zase na úrovni statických tříd jako statické metody těchto tříd. Navíc se v C# neoznačují atributem ExtensionAttribute, ale místo toho se první parametr označí klíčovým slovem this. Výsledek kompilace metody je ale stejný jak z VB tak z C# a Visual Basic umí použít i extension metody deklarované v něčem jiném než ve standardním modulu. Zajímavé je, že kompilátory obou jazyků označí třídu (modul), ve které(m) se rozšiřující metody nacházejí také atributem ExtensionAttribute.
Ukázka
Public Module StringTools
Public Function ToCyrilic(str As String) As String
' ...
End Function
End Module
Public Module KdeToChciPoužít
Public Sub Main()
Dim a As String = "ABC"
Console.WriteLine(a.ToCyrilic) 'Vypíše АБЦ (kdyby to nějdo implementoval)
End Sub
End Module
Rozšiřující lze deklarovat pouze metody (Sub, Function) nikoliv třeba vlastnosti.
Partial methods jsem si pracovně přeložil jako "roztrhané metody". Jedná se o metodu (pouze sub a pouze private), která má oddělenou definici a deklaraci. Tento přístup je znám zejména z jazyka C++, který vyžaduje aby deklarace metody vždy předcházela jejímu použití. Ve Visual Basicu toto nutné není, ale může se to hodit ve chvíli, kdy definici (tedy tu část s kódem) generuje nějaký generátor a tak není dostupná v době psaní kódu, což generuje chybové hlášky a metoda chybí v IntelliSensu. To vás může vést k tomu, že si metodu v jedné části partial třídy deklarujete a v jiné se pak "sama" definuje (ale jde to i v rámci jedné části partial třídy). Partial metody můžete používat i v rámci struktur a modulů. U modulů to, ale asi moc smysl nemá, protože modul jako takový se nemůže skládat z více částí.
Vypadat to bude asi takto:
Class [MyClass]
Private Partial Sub MySub() 'Deklarace
End Sub
'------------------------------------------------
Private Sub MySub() 'Definice
'Kód
End Sub
End Class
Je poněkud podivné, že na rozdíl od MustOverridemetod, deklarace partial metod musí obsahovat i End Sub(a prázdné tělo), což poněkud snižuje přehlednost kódu. To že partial metody mohou být jen private subje ještě více omezující. Bylo by jistě zajímavé, kdyby to mohly být i funkce a vlastnosti (případně custom události) a nemusely být private.
A teď ta největší podivnost: Co si myslíte, že se stane, když zapomenete k deklaraci roztržené metody dopsat její definici (tělo, kód)??? Chyba při kompilaci? Ne! Nestane se nic! Vůbec nic! Metoda se dokonce ani neobjeví ve zkompilované assembly jako prázdná. Prostě tam vůbec nebude! A co se stane s voláním těchto metod? Zmizí! Vůbec se nezkompilují! Pokud výraz, kterým se vypočítával parametr předávaný metodě měl nějaký vedlejší efekt, tak k tomuto vedlejšímu efektu nedojde. Výraz ve výsledné assembly prostě nebude. Pokud se pokusíte na roztrženou metodu zavolat AddressOf, tak vám to projde jen v případě, že existuje definice. Pokud definice neexistuje, je to chyba při kompilaci (která se ve VS ihned modře podtrhne). Zajímalo b y mne jestli toto chování, je to co autoři chtěli, nebo se to do finální verze ještě změní. Zkusím pogooglit a dám vědět.
Jedná se o velmi podobnou vlastnost, která je ve VB8 k dispozici pro inicializaci atributů aplikovaných na nějaké položky. Lehkou záhadou zůstává, proč nebyla použita stejná syntaxe jako u atributů. Asi proto, abyste v konstruktorech "neatributů" mohli používat pojmenované parametry.
Podívejte se na následující kód:
Public Class MyCls : Inherits Attribute
Public Sub New()
End Sub
Public Sub New(ByVal RW As String)
End Sub
Public Property RW() As String
Get
End Get
Set(ByVal value As String)
End Set
End Property
Public ReadOnly Property RO() As String
Get
End Get
End Property
Public WriteOnly Property WO() As String
Set(ByVal value As String)
End Set
End Property
Public ReadOnly ROf As Long
Public RWf As Long
End Class
<MyCls(RW:="RW", RWf:=11L, WO:="WO")> _
Public Module M
Sub m()
Dim x = New MyCls("RW") With {.RW = "4", .WO = "A", .RWf = 4L}
End Sub
End Module
Na před-předposledním řádku vidíte inicializaci hodnot dostupných vlastností a proměnných objektu typu MyCls. To, že používám konstruktor, který vypadá jako, že nastavuje hodnotu vlastnosti RW, vůbec nevadí, v inicializaci jí mohu nastavit znovu, na co chci jiného. Třídu MyClsjsem záměrně oddědil od Attribute, abych mohl ukázat rozdíl oproti inicializaci atributů. Tam není na první pohled zřejmé, jestli se použije konstruktor bez parametrů a nastaví se vlastnost RW, nebo se použije konstruktor s jedním parametrem, který se jmenuje také RWa vlastnost RWse nenastaví. Skutečnost je taková, že se použije konstruktor bez parametrů. Pokud chcete použít konstruktor s parametrem, musíte zadat jeho parametry pozičně (<MyCls("RW", RW:="RW")>). V inicializaci atributu nelze stejně jako ve VB8 používat pojmenované argumenty (a nefunguje tam doplňování názvů vlastností - IntelliSense). Při "obyčejné" ("nové") inicializaci, pojmenované argumenty klidně použít můžete:
Dim x = New MyCls(RW:="RW") With {.RW = "4"}
Tato nová fičura vám jistě usnadní pasní kódu, ale nemusela by být až tak "ukecaná" (C# vynechává Witha tečky před názvy členů). Škoda je, že tento způsob nelze použít pro inicializaci kolekcí (třeba List(Of T)) jako v C#, ale jen polí.
Znáte to, deklarujete proměnnou Dim x = "Ahoj"a pokud nemáte projekt nastavený prasácky, hned na vás vyskočí warning Variable declaration without an 'As' clausule; type of Object assumed.. Mě osobně to ještě otravnější připadá u konstant. Což o to, u některých jednoduchých typů si pomohu typovým znakem Dim x$ = "Ahoj". Ale typových znaků je sakra málo, a tak je nutné dopsat 'As' klauzuli. Přitom každému trotlovi je i bez ní jasné, že je to String! Takže i VB9 přestal být trotl a nyní, když deklarujete proměnnou (konstantu) Dim x = "Ahoj", pochopí, že je typu String.
Ale nic není tak jednoduché, jak by mohlo být :-(. Podívejte se na následující ukázku a sledujte typy napsané v komentářích:
Module Modul
Public Const C1 = 4L 'Long
Public Const C2 = 4US 'UShort
Public Const C3 = CByte(11) 'Byte
Public Const C4 = "Ahoj" 'String
Public Const C5 = "Đ"c 'Char
Public Const C6 = Nothing 'Object
Public Const C7 = AscW("Đ"c) 'Integer
Public Const C8 = #10/10/2007# 'Date
Public Const C9 = CType(11, SByte) 'SByte
Public Const CA = CType(Nothing, String) 'String
Public Const CB = CType(Nothing, List(Of Long)) 'Object
Public Const CD As List(Of Long) = Nothing '<chyba - toto nelze>
Private F1 = 4L 'Object
Public F2 = New List(Of Integer)(New Integer() {1, 2}) 'Object
Public F3 = "Ahoj" 'Object
Public F4 = "Ahoj" + " " + "světe".ToString 'Object
Public F5 = New TimeSpan(10, 10, 0) - New TimeSpan(10, 9, 0) 'Object
Sub Test()
Const C1 = 4L 'Long
Const CB = CType(Nothing, List(Of Long)) 'Object
Dim F1 = 4L 'Long
Dim F2 = New List(Of Integer)(New Integer() {1, 2}) 'List(Of Integer)
Dim F3 = "Ahoj" 'String
Dim F4 = "Ahoj" + " " + "světe".ToString 'String
Dim F5 = New TimeSpan(10, 10, 0) - New TimeSpan(10, 9, 0) 'TimeSpan
Dim F6 = CInt(CB) + C1 'Integer
End Sub
End Module
Vidíte, že chování této fičury je poněkud inkonzistentní. Funguje pro jakékoliv výrazy ale jen pro lokální proměnné, a konstanty (lokální i globální) V originále [1] se tato fičura dokonce jmenuje local type inferencing. Z toho by vyplývalo, že bude fungovat jen pro lokální proměnné a konstanty. Ona funguje i pro nelokální konstanty, ale nefunguje pro nelokální proměnné (fields). Další záhada VB9 Beta 2.
Anonymní typy jsou typy, které nemají jméno. Jedná se opět o fičuru, která je přítomna i v C# a má především zjednodušit zápis kódu. Proměnná anonymního typu může být jen lokální a vznikne podobně jako při
inicializaci členů, akorát vynecháte název typu.
Dim CementaryCustomer = New With { .Name = "Karel Barel", .Age = 110}
Definice říká, že proměnná anonymního typu je typu třídy, která nemá žádné jméno. Tato třída dědí přímo od Objectu. Typ vlastností je odvozen stejně jako u lokálních proměnných tato třída předefinovává metodu ToString(), tak aby vracela výraz, kterým byl anonymní objekt vytvořen. Zajímavé. Zvláště to o tom ToString. Takže se na to raději podíváme v praxi. Následujíc kód nám pomůže zjistit, co se za tím skrývá:
Dim Simple = New With {.Jméno = "Karel Barel", .Věk = 110}
Dim NotSoSimple = New With {.Jméno = "James" & " " & "Bond", .věk = CInt(4 * Math.PI)}
Dim Difficult = New With { _
.Jméno = (New String() {"Pepa ZDepa", "Olgoj Chorchoj"})(1), _
.věk = New Random(14).Next(1, 177)}
Console.WriteLine("Simple {0}; {1}", Simple, Simple.GetType.FullName)
Console.WriteLine("NotSoSimple {0}; {1}", NotSoSimple, NotSoSimple.GetType.FullName)
Console.WriteLine("Difficult {0}; {1}", Difficult, Difficult.GetType.FullName)
Console.WriteLine(Simple.GetType.Equals(Difficult.GetType))
Výstupem je něco takovéhoto:
Simple { Jméno = Karel Barel, Věk = 110 }; ...
NotSoSimple { Jméno = James Bond, Věk = 13 }; ...
Difficult { Jméno = Olgoj Chorchoj, Věk = 8 }; ...
True
Jako typ (...) se pokaždé vypsalo to samé: VB$AnonymousType_0`2[[System.String,mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]. To je dobrá správa, protože nám to říká, že stejně definované anonymní typy jsou to samé. Předpona VB nás ale varuje, že C#, tam dá asi jinou předponu ;-). Například f__. Pokud anonymní typ definujete s jinými názvy vlastností, také se nebude jednat o ten samý, ale například o VB$AnonymousType_1. To, že s jinými názvy vlastností se jedná o různé typy a se stejnými o stejné (pokud jsou vlastnosti stejného typu), ale platí jen v rámci jedné assembly! Číslo záleží na pořadí kompilace typů v rámci assembly. To znamená, že pokud byste porovnávali více anonymních typů z různých assembly, tak můžete zjistit, že stejné jméno obdržely typy s různými názvy vlastností (a různé se stejnými).
Výpis hodnot vlastností anonymního typu používá metodu ToStringaktuálního typu vlastnosti.
Poslední poznámka k této variantě anonymních typů: Při číslování je Visual Basic case insensitive a velikost písmen vlastností je odvozena z prvního kompilovaného typu.
Anonymní typy s klíčem
U anonymních typů popsaných výše jsou jejich vlastnosti Read-Write. Pokud však před název vlastnosti dáte klíčové slovo Key, bude taková vlastnost jen pro čtení.
Dim a = New With{Key .Prp = 209}
Jako odměnu za to, že vlastnost je jen read-only, VB tuto vlastnost začne chápat jako identifikátor (klíč; odtud Key) instance typu a takto deklarovaný anonymní typ automaticky implementuje System.IEquatable(Of T)a předefinovává Equals(typově bezpečnou i nebezpečnou) a GetHashCode.
Klíčů můžete mít i více.
Závěr k anonymním typům
Na závěr musím dodat, že anonymní typy jsou plně řešeny již při kompilaci, takže jejich používání nemá žádný negativní dopad na výkonnost aplikace.
A na úplný závěr jsem se podíval na anonymní typy disassemblerem v Reflectoru, abych zjistil jak opravdu vypadají. Povšimněte si, že názvy některých věcí nesplňují kritéria pro názvy, takže se k nim není možno dostat jinak než přes reflection.
Bez klíče
<DebuggerDisplay("\{ Jm" & ChrW(233) & "na = {Jm" & ChrW(233) & "na}, v" & ChrW(283) & "ky = {v" & ChrW(283) & "ky} \}", Type:="<anonymous type>"), CompilerGenerated> _
Friend Class VB$AnonymousType_1(Of T0, T1)
' Methods
<DebuggerNonUserCode> _
Public Sub New(ByVal Jména As T0, ByVal věky As T1)
Me.$Jména = Jména
Me.$věky = věky
End Sub
<DebuggerNonUserCode> _
Public Overrides Function ToString() As String
Return ("{ " & String.Format("{0} = {1}, ", "Jm" & ChrW(233) & "na", Me.$Jména) & String.Format("{0} = {1} ", "v" & ChrW(283) & "ky", Me.$věky) & "}")
End Function
' Properties
Public Property Jména As T0
<DebuggerNonUserCode> Get
Return Me.$Jména
End Get
<DebuggerNonUserCode> Set(ByVal Value As T0)
Me.$Jména = Value
End Set
End Property
Public Property věky As T1
<DebuggerNonUserCode> Get
Return Me.$věky
End Get
<DebuggerNonUserCode> Set(ByVal Value As T1)
Me.$věky = Value
End Set
End Property
' Fields
Private $Jména As T0
Private $věky As T1
End Class
S klíčem (resp. se 2 klíči)
<DebuggerDisplay("\{ jm" & ChrW(233) & "no = {jm" & ChrW(233) & "no}, v" & ChrW(283) & "k = {v" & ChrW(283) & "k} \}", Type:="<anonymous type>"), CompilerGenerated> _
Friend Class VB$AnonymousType_2(Of T0, T1)
Implements IEquatable(Of VB$AnonymousType_2(Of T0, T1))
' Methods
<DebuggerNonUserCode> _
Public Sub New(ByVal jméno As T0, ByVal věk As T1)
Me.$jméno = jméno
Me.$věk = věk
End Sub
<DebuggerNonUserCode> _
Public Function Equals(ByVal val As VB$AnonymousType_2(Of T0, T1)) As Boolean Implements IEquatable(Of VB$AnonymousType_2(Of T0, T1)).Equals
If (val Is Nothing) Then
Return False
End If
Dim e_ As VB$AnonymousType_2(Of T0, T1) = val
If (Me.$jméno Is Nothing) Then
If (Not e_.$jméno Is Nothing) Then
Return False
End If
ElseIf (e_.$jméno Is Nothing) Then
Return False
End If
If (IIf((((Not Me.$jméno Is Nothing) AndAlso (Not e_.$jméno Is Nothing)) AndAlso Not Me.$jméno.Equals(e_.$jméno)), 1, 0) <> 0) Then
Return False
End If
If (Me.$věk Is Nothing) Then
If (Not e_.$věk Is Nothing) Then
Return False
End If
ElseIf (e_.$věk Is Nothing) Then
Return False
End If
If (IIf((((Not Me.$věk Is Nothing) AndAlso (Not e_.$věk Is Nothing)) AndAlso Not Me.$věk.Equals(e_.$věk)), 1, 0) <> 0) Then
Return False
End If
e_ = Nothing
Return True
End Function
<DebuggerNonUserCode> _
Public Overrides Function Equals(ByVal obj As Object) As Boolean
Return Me.Equals(TryCast(obj,VB$AnonymousType_2(Of T0, T1)))
End Function
<DebuggerNonUserCode> _
Public Overrides Function GetHashCode() As Integer
Dim num As Integer = &H2BE025C7
num = (num * -1521134295)
If (Not Me.$jméno Is Nothing) Then
num = (num + Me.$jméno.GetHashCode)
End If
num = (num * -1521134295)
If (Not Me.$věk Is Nothing) Then
num = (num + Me.$věk.GetHashCode)
End If
Return num
End Function
<DebuggerNonUserCode> _
Public Overrides Function ToString() As String
Return ("{ " & String.Format("{0} = {1}, ", "jm" & ChrW(233) & "no", Me.$jméno) & String.Format("{0} = {1} ", "v" & ChrW(283) & "k", Me.$věk) & "}")
End Function
' Properties
Public ReadOnly Property jméno As T0
<DebuggerNonUserCode> Get
Return Me.$jméno
End Get
End Property
Public ReadOnly Property věk As T1
<DebuggerNonUserCode> Get
Return Me.$věk
End Get
End Property
' Fields
Private ReadOnly $jméno As T0
Private ReadOnly $věk As T1
End Class
6. Lambda (λ) expressions
Lambda metoda je anonymní (bezejmenná) metoda, která umožňuje "nacpat" in-line kód kamkoliv, kde je akceptován delegát.[1] Její deklarace vypadá asi takto:
Dim f = Function(x As Integer) x + 1
Význam tohoto je něco jako:
Delegate Function df(x As Integer) As Integer
Private Function ff(x As Integer) As Integer
Return x + 1
End Function
Sub xxx()
Dim f As df = AddressOf ff
End Sub
Jak sami musíte uznat, ušetří to kvantus kódu, který by bylo nutné napsat pro vytvoření něčeho tak trapného jako funkce přičítající jedničku! Přitom takto trapné funkce jsou poměrně často potřeba - posílají se do různých porovnávacích rutin (na příklad).
Lambda expression nemůže mít optional (volitelné) parametry ani parametry typu ParamArray. ByVali ByRefje povoleno.
Zajímavé jsou možnosti neurčování typů pro lambdy. Nejen že nelze nijak natvrdo určit návratový typ lambda funkce, ale též lze neurčit typ parametru a přesto bude znám.
Dim f = Function(x) x + 1 'Warning Variable declaration without ... a ještě jeden u '+'
Dim f As dInt = Function(x) x + 1 'Předpokládejme Delegate Function dInt(ByVal x As Integer) As Integer, pak je vše v pořádku a typ x je znám
Lambda funkce je možné deklarovat i mimo procedury (přiřazovat je k instančním/třídním proměnným typu delegát). Lambda funkce mají přístup k instanci ve které byly deklarovány (např. k privátním proměnným) a pokud jsou deklarovány v rámci metody mají přístup i k lokálním proměnným této metody.
Class n
Private g As Long
Sub ň()
Dim h As Integer
Dim t = Function() g + h
End Sub
End Class
Closures
Klozůry - opravdu mě nenapadá jak to přeložit ;-)
Jak již bylo zmíněno, lambda výrazy mají přístup k lokálním proměnným funkcí. Jenomže lambda výraz může žít déle než funkce, k jejímž lokálním proměnným má výraz přístup. Z toho vyplívá potřeba neumisťovat takovéto lokální proměnné na zásobník ale na haldu (heap). Za tímto účelem vytvoří kompilátor třídu, do které uloží lokální proměnné, které lambda používá a také referenci na instanci třídy, ke které má mít lambda přístup (pokud to vyžaduje).
Používání "klozůr" koliduje s použitím příkazů skoku a proto jsou v některých případech zakázány. "Klozůra" totiž obdrží hodnoty lokálních proměnných ve chvíli, kdy kód vstoupí do bloku, kde je deklarována. Z toho důvodu není možné provádět skok to takovéhoto bloku. (Ale kdo dnes používá GoToa Resume!?)
Abyste si náhodou z předchozího tvrzení nemysleli, že "klozůra" obdrží kopii lokální proměnné, tak věřte, že následující kód vypíše 102.
Dim h As Integer = 11
Dim t = Function() h
h = 102
Console.Write(t.Invoke)
Expression tree (strom výrazu) je kód, který nebyl přeložen do binární formy (MSIL), ale namísto toho byl přeložen do stromu, který určuje strukturu výrazu. Takto uložený výraz lze pak přeložit do jiného jazyka (např. SQL). Stromy výrazů (nazýváno též "kód jako data") se používají hlavně při LINQu oproti nějakému externímu zdroji dat - například SQL serveru. Stromy výrazů jsou podobné CodeDOM reprezentaci programu, ale obsahují jen výraz, nikoli celý program. I když kdo ví. Jazyk F# umožňuje uložit tak i větší bloky kódu. Třeba se někdy dočkáme doby, kdy půjde Visual Basic "zkompilovat" do JavaScriptu nebo do T-SQL uložené procedury...
Ve VB máme nějaké výrazy, které mají nějaký typ, něco znamenají a něco dělají. V průběhu jejich vyhodnocování dochází k tzv. reklasifikaci. Například přístup k lokální proměnné je nahrazen (reklasifikován na) její hodnotou, volání funkce návratovou hodnotou atp. Speciálním případem této reklasifikace je reklasifikace labmda výrazu. Ten může být jednak reklasifikován na parametr konstruktoru delegáta (jak bylo ukázáno výše), ale pokud jeho cílovým typem je System.Linq.Expressions.Expression(Of T)a Tje delegát, tak lambda výraz je interpretován, jako by byl použit při konstrukci delegáta Ta pak je konvertován na strom výrazu. Specifikace [1] nespecifikuje, jak přesně se budou výrazy do stromu výrazů překládat :-( a negarantuje, že to bude konzistetní mezi jednotlivými budoucími verzemi VB.
Jedná se o větší flexibilitu při používání delegátů. Jsou možná některá přiřazení, která dříve možná nebyla.
Podívejte se co vše VB9 umožňuje. V komentářích je uvedeno jaký názor na to měl VB8.
Delegate Sub D1(ByVal a As Integer)
Delegate Sub D2(ByVal a As Long)
Delegate Sub D3(ByVal a As Integer, ByVal b As Object)
Private Sub S1(ByVal a As Integer)
End Sub
Private Sub S2(ByVal a As Long)
End Sub
Private Sub S3(ByVal a As Integer, Optional ByVal b As Object = Nothing)
End Sub
Private Sub Test()
Dim I1 As D2 = AddressOf S1 'Does no have same signature
Dim I2 As D1 = AddressOf S2 'Does no have same signature
Dim I3 As D1 = AddressOf S3 'Does no have same signature
End Sub
Private Event E1 As D1
Private Event E2 As D2
Private Event E3 As D3
Private Sub H1(ByVal a As Integer) Handles Me.E2 'Do not have same signature
End Sub
Private Sub H2(ByVal a As Long) Handles Me.E1 'Do not have same signature
End Sub
Private Sub H3(ByVal a As Long, Optional ByVal b As Object = Nothing) Handles Me.E3 'Do not have same signature
End Sub
Private Event MouseMove As EventHandler(Of System.Windows.Forms.MouseEventArgs)
Private Sub OnMouseMove(ByVal Sender As System.Windows.Forms.Button, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles Me.MouseMove 'Do not have same signature
End Sub
Nulovatelné typy byly již v .NET frameworku 2.0. VB9 ale zjednodušuje práci s nimi. To co jste dříve museli deklarovat jako Nullable(Of T)dnes deklarujete jako T?. Jedná se pouze o syntaktickou zkratku, takže platí všechno co platilo pro Nullabledoposud. Především to, že nulovatelné mohou být jen struktury. Kód napsaný starým a novým způsobem je plně zaměnitelný.
VB9 kromě této syntaktické zkratky přidává ještě další zjednodušení.
- Není dosud implementováno: Ke všem členům typu
Tmůžete přistupovat přímo z T?
- Automatická konverze ze základního do nulovatelného typu a naopak.
- Odvozené konverze. Pokud typ
Tmá konverzi do S, tak T?také. T?má pak i konverzi do S?. Pokud Smá konverzi do T, pak Smá i konverzi do T?a S?do T?. Konverze z/do S?jsou samozřejmě možné jen pokud Sje struktura. Pokud Sje třída a T?.HasValueje false, tak se T?na Spřetypuje jako Nothing.
- Dvojí deklarace jako u polí.
Dim a As Long?a Dim a? As Long. Dim a? As Long?je chyba, stejně jako u polí.
Nikde jsem o tom doposud neslyšel. Nikdo to halasně neprezentoval. A přitom je to největší změna ve Visual Basicu od jeho vzniku!!! :-) VB programátoři konečně dostávají do ruky ternární operátor! Ale nebyl by to Visual Basic, kdyby se jednalo o operátor ?:. Operátor je realizován velmi podobně jako nechvalně známá funkce Iif, ale jmenuje se If. Má 2 podoby. Se třemi a dvěma argumenty.
První podoba
If(Contidion, TruePart, FalsePart)
je klasický ternární operátor, který při splnění podmínky vyhodnotí TrueParta při nesplnění FalsePart. Narozdíl od Iifa podobně jako u AndAlsoa OrElsemůžete používat i výrazy, které při splnění/nesplnění podmínky skončí chybou. Vyhodnotí se jen ta část, která se vyhodnotit musí, takže k chybě nedojde.
Druhá podoba
If(Object1, Object2)
slouží k ošetření nullových hodnot. Pokud Object1je Nothingvrací Object2, jinak vrací Object1. Funguje i pro Nullablea druhou část vyhodnocuje jen když musí. Je to zkratka k If(Object1 Is Nothing, Object1, Object2), ale není jí plně ekvivalentní. Výraz na místě Object1 je vyhodnocen jen 1×. A pokud Object1je T?a Object2je "bez otazníku" a typu struktura, tak při rozhodování o typu návratové hodnoty se Object1chápe jako T.
Jak již tedy vyplynulo, Ifje na rozdíl od Iiftypově bezpečný (v obou variantách).
Zajímavostí je, že If(x, Nothing, Nothing)by mělo způsobit chybu při kompilaci. V praxi jsem však ověřil, že to takto není (zatím) implementováno.
LINQ = Language Integrated Query (dotaz integrovaný do jazyka) umožňuje vykonávat deklarativně zapsané (SQL-like) dotazy jednak přes jakékoliv kolekce v .NETu a také přes datové zdroje které umí interpretovat stromy výrazů. LINQ je poměrně rozsáhlé téma, takže podrobný popis si necháme na jindy. Nejdřív ho musim já sám pochopit ;-).
System.Runtime.CompilerServices.InternalsVisibleToAttributeje atribut, který lze aplikovat na úrovni assembly a inicializovat jej na název assembly, které získá přístup ke všem elementům z assembly, na níž je aplikován, a které jsou deklarovány jako Friend. Název assembly, pro kterou garantujete přístup může být buď silný (s verzí a tokenem) nebo slabý (jen název). Atribut můžete použít několikrát a garantovat tak přístup více spřáteleným sestavením (friend assemblies).
To se hodí v případě, kdy chcete například velkou knihovnu rozdělit do několika assembly a jednotlivým assembly dát přístup ke členům, ke kterým ale nemá mít přístup uživatel knihovny. Také se to může hodit, pokud programujete nějaké testy pro vaši knihovnu a tyto testy nají mít přístup k neveřejným položkám knihovny. VB9 nově tento atribut rozpoznává a validuje jej již v době návrhu.
Assembly Project1
<Assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Project2")>
Friend Module M()
Public Sub S()
End Sub
End Module
Assembly Project2
Module M2
Public Sub Main()
Project1.M.S()
End Sub
End Module
Assembly Project2má přístup k Friendprvkům z assembly Project1.
Skupinové třídy jsou vlastnost kompilátoru, kterou většinou využívá jen on sám. Ale můžete ji použít také, pokud k tomu najdete nějaký důvod. Tímto způsobem je možno zpřístupnit defaultní instance objektů - tak jako máte přístup k defaultní instanci všech formulářů ve WinForms aplikaci.
Defaultní instance formulářů jsou Visual Basicem vyráběny takto:
<Microsoft.VisualBasic.MyGroupCollection("Form", "Create", "Dispose", "My.Forms")> _
Public NotInheritable Class MyForms
Private Shared Function Create(Of T As {New, Form}) _
(Instance As T) As T
If Instance Is Nothing Then
Return New T()
Else
Return Instance
End If
End Function
Private Shared Sub Dispose(Of T As Form)(ByRef Instance As T)
Instance.Close()
Instance = Nothing
End Sub
End Class
Uvedený kód zkompiluje takto:
<Microsoft.VisualBasic.MyGroupCollection("Form", "Create", "Dispose", "My.Forms")> _
Public NotInheritable Class MyForms
Private Shared Function Create(Of T As {New, Form})(Instance As T) As T
If Instance Is Nothing Then
Return New T()
Else
Return Instance
End If
End Function
Private Shared Sub Dispose(Of T As Form)(ByRef Instance As T)
Instance.Close()
Instance = Nothing
End Sub
Private m_Form1 As Form1
Public Property Form1() As Form1
<DebuggerNonUserCode> Get
Return Create(m_Form1)
End Get
<DebuggerNonUserCode> Set (Value As Form1)
If Value IsNot Nothing AndAlso Value IsNot m_Form1 Then
Throw New ArgumentException("Property can only be set to Nothing.")
End If
Dispose(m_Form1)
End Set
End Property
End Class
Kompilátor do třídy označené atributem MyGroupCollectionAttributeautomaticky pro všechny třídy v projektu, které dědí od Formdoplní vlastnosti pro získání jejich defaultních instancí. Defaultní instance jsou inicializovány metodou Createa destruovány metodou Disposea jsou dostupné přes defaultní instanci třídy MyForms, která je dostupná přes My.Forms. Pokud to budete chtít použít samy, tak poslední parametr konstruktoru atributu je ignorován. Defaultní instanci třídy, která defaultní instance zpřístupňuje, si musíte vytvořit samy.
Tato vlastnost byla dostupná již ve VB8. VB9 ji rozšiřuje o možnost zadat do prvních 3 parametrů konstruktoru atributu čárkou oddělený seznam. Počty položek v čárkou oddělených seznamech u jednotlivých parametrů musí souhlasit. Jejich význam je takový, že do dané třídy jsou vytvořeny vlastnosti vracející výchozí instance všech typů, které dědí od typů uvedených v čárkou odděleném seznamu prvního parametru. Tyto typy jsou inicializovány metodami uvedenými na odpovídajících pozicích čárkou odděleného seznamu u druhého parametru a destruovány metodami od třetího parametru. VB9 také přidává podporu pro generické typy - tedy jen pro negenerické typy, které dědí od generických typů.
Pokud deklarujete proměnnou typu interface, máte u takovéto proměnné přístup ke všem metodám typu Object. To ve VB8 nebylo. Pokud v interfacu deklarujete něco, co se jmenuje stejně jako metoda třídy Object, je metoda třídy Objectimplicitně zastíněna (shadowed) (a nelze tomu nijak zabránit).
Tato vlastnost, nevím proč se o ní Beta 2 dokumentace [1] nezmiňuje (informace najdete třeba na [2]), vám umožňuje integrovat XML přímo do zdrojového kódu Visual Basicu. Můžete deklarovat proměnnou typu System.Xml.Linq.XDocumenta přiřadit jí XML dokument. Jedná se o opravdový XML dokument, žádný String, žádné uvozovky. Můžete vytvořit ještě literály typu XProcessingInstruction, XElement, XCommenta XCdata. Atributy a textové uzly nejsou možné.
Ke XML ve Visual Basicu se váže ještě jedna věc. A to příkaz Import, který nově umožňuje importovat jmenný prostor XML. A to jak defaultní tak pojmenovaný.
Imports <xmlns:html="http://www.w3.org/1999/xhtml">
Dim i = <?xml version="1.0" encoding="utf-8"?>
<html:html>
<html:head>
<html:title>Pokus</html:title>
</html:head>
<html:body>
<html:p>Pokus</html:p>
</html:body>
<html:html>
<?test test?> 'XDocument
Dim a = <?ahoj?> 'XProcessingInstruction
Dim b = <?xml version="1.0"><root/> 'XDocument
Dim c = <ahoj/> 'XElement
Dim d = <nazdar><bazar/></nazdar> 'XElement
Dim k = <--koment--> 'XComment
Dim cd = <![CDATA[Test]]> 'XCData
Dim err = <?xml version="1.0"> 'Chyba, očekáván root
Jak jste si jistě všimli, Visual Basic nevyžaduje v XML na konci řádku podtržítko. Visual Basic sám pozná, kde XML končí, když uzavřete první element. Za uzavření nejvnějšnějšího elementu vám dovolí umístit ještě procesní instrukce (jak ukazuje HTML příklad; platí jen pro XDocument). Určení verze XML 1.0je povinné a musí být 1.0. Editor vám sám dělá odsazení uvnitř XML a kontroluje zda je well-formed. Intellisense mi nefungoval, ale nápověda [2] jej slibuje, pokud si přidáte příslušný XSD dokument do projektu. Nevím jak to bude s validací XML oproti schématu. Na DTD zapomeňte. Tato zastaralá technologie není podporována a do integrovaného XML ani nemůžete zapsat DOCTYPE.
Editor za vás dělá ještě tu zajímavou věc, že když změníte název otevíracího tagu, změní sám název uzavíracího. Pokud změníte název uzavíracího, vrátí jej zpět.
Visual Basicové komentáře (uvozené apostrofem ') můžete dávat jen za konec XML. Pokud do XML zadáte podtržítko (jako pokračování řádku) nebo apostrof (jako komentář), budou se brát jako textové uzly XML a stanou se jeho součástí.
A protože XML je objekt, můžete udělat například toto:
Dim h = <?xml version="1.0"><root/>.Document
S proměnnými typu XDocument a XElement můžete dělat další psí kusy (a tam již Intellisense funguje). Pro následující příklad si přestavte proměnnou i z prvního XML příkladu, ale s atributem u hlavičky navíc <html:head id="myh">:
Dim heads = i...<html:head> 'Všichni potomci typu html:head
Dim hid = i...<html:head>.@id 'Atribut id elementu head ("myh")
Visual Basic tedy definuje vlastnosti pro XML osy.
| osa |
vlastnost |
použití |
| attribute |
@ |
object.@attribute
object.<attribute> |
| child |
. |
object.<child> |
| descendant |
... |
object...<descendant> |
Další osy (jako parent) jsou dostupné textově (přes vlastnosti a metody XML objektů). Ve Visual Basicu tedy máte něco jako XPath.
Visual Basic také umožňuje XML konstruovat. Zde se protínají světy XML a LINQ. V nápovědě [2] najdete následující ukázku:
Dim phones As XElement = _
<phones>
<phone type="home">206-555-0144</phone>
<phone type="work">425-555-0145</phone>
</phones>
Dim phoneTypes As XElement = _
<phoneTypes>
<%= From phone In phones.<phone> _
Select <type><%= phone.@type %></type> _
%>
</phoneTypes>
Console.WriteLine(phoneTypes)
Jejím výsledkem je:
<phoneTypes>
<type>home</type>
<type>work</type>
</phoneTypes>
Jak vidno XML ještě v kombinaci s LINQ bude ve VB mocným nástrojem. Takže se na něj třeba někdy podíváme podrobněji.
Jen ve stručnosti zmíním další změny, na které jsem narazil a které nejsou ani tak změnami jazyka jako změnami jeho integrace s IDE Visual Studia.
Vylepšený intellisense
Intellisense nyní ve Visual Basicu zobrazuje i klíčová slova a poskytuje vám tak kompletní nápovědu při psaní kódu.
XML dokumentace
Se SP1 pro VS 2005 přišla pro programátory ve Visual Basicu nepříjemná změna: Generované soubory s XML komentáři neobsahovali korektní informace o typu v atributech crefelementů jako <see>nebo <exception>. Tím bylo znemožněno procházení XML dokumentace a použití nástrojů jako SandCastle. Tato chyba byla konečně (!) opravena.
Multitargeting
Multitargeting je nová vlastnost Visual Studia 2008 (težko říci jestli se jedná o vlastnost Visual Studia nebo jazyka; takový Phalanger nebo IronPython ji podporoval již dávno), která umožňuje vybrat si cílovou platformu, pro kterou se má kód buildovat. Výběr platformy souvisí s verzí kompilátoru vbc, která se použije. Na nižších verzích platforem pak nemáte k dispozici některé třídy a rovněž některé vlastnosti jazyka, které byly přidány až později. Toto si můžete ošetřit například podmíněným překladem, kde přibyla nová konstanta #Const VBC_VER.
Pokusil jsem se shrnout všechny změny jazyka Visual Basic 9 oproti verzi 8 (2005 -> 2008). Jsem si vědom toho, že jsem poněkud "odflákl" LINQ a XML. Tento článek je i tak poměrně dlouhý a mě to dává prostor pro napsání ještě dalších dvou článků ;-). Také jsem letmo zmínil novinky v integraci s Visual Studiem. Ty by si ale také zasloužili ještě podrobnější studium. Při zkoušení nových vlastností jazyka jsem narazil na některé "zvláštnosti", o kterých si nejsem jist jestli jsou záměrem nebo bugem beta verze. Uvidíme časem.
Visual Basic 9 je jistě krok dobrým směrem a posouvá programování zase o kus dál. V porovnání s jazykem C# se VB drží docela dobře. Má některé vlastnosti navíc (automatické odvození typu, XML). Některé jiné by mohly být ve VB dotaženy k větší dokonalosti (inicializátory). Některé nemá vůbec (<Field: Attr>, automatické gettery a settery).
Rád bych ještě vyzval všechny, kdo si nainstalovali pre-release verzi Visual Studia (ale i budoucí uživatele stabilní verze), aby využili stránky Microsoft Connect a reportovali Microsoftu vše, co považují za bug nebo nedokonalost. Z vlastní zkušenosti mohu říci, že si to minimálně někdo přečte a rozhodne se jestli se tím bude nebo nebude zabývat a kdy. Můžeme tak pomoci udělat náš Visual Basic ještě lepším, až nejlepším!
Vivat Visual Basic
Đonny