Visão geral do Visual Basic 9.0

Artigos técnicos sobre o Visual Studio 2005

Publicado em: 10 de abril de 2007

por Erik Meijer, Amanda Silver, Paul Vick

Microsoft Corporation

 

Resumo: Fornece uma visão geral de novos recursos do Visual Basic e novas extensões de linguagem que oferecem suporte para a programação intensiva de dados (17 páginas impressas).

Conteúdo

Nesta página

Introdução Introdução
Introdução ao Visual Basic 9.0 Introdução ao Visual Basic 9.0
Variáveis de local digitadas implicitamente Variáveis de local digitadas implicitamente
Inicializadores de objeto e de matriz Inicializadores de objeto e de matriz
Tipos anônimos Tipos anônimos
Suporte de nível profundo a XML Suporte de nível profundo a XML
Compreensão de consulta Compreensão de consulta
Métodos de extensão e expressões lambda Métodos de extensão e expressões lambda
Tipos anuláveis Tipos anuláveis
Delegados relaxados Delegados relaxados
Conclusão Conclusão

Introdução

O Visual Basic sempre procurou criar aplicativos de linha de negócios pragmáticos, orientados aos dados. Embora a mudança para o .NET tenha adicionado o poder de uma estrutura unificada e uma plataforma gerenciada ao desenvolvedor de aplicativos, a próxima versão do Visual Basic inclui um conjunto de recursos que afetarão profundamente a produtividade do desenvolvedor ao criar aplicativos orientados aos dados. Essas extensões de linguagem apresentam recursos de consulta de propósito geral que se aplicam a todas as fontes de dados, sejam gráficos de objeto hierárquicos, relacionais ou documentos XML.

Este documento é uma visão geral desses novos recursos. Para obter mais informações, incluindo atualizações para a definição de linguagem do Visual Basic visualizações de compilador, visite o Visual Basic Developer Center (https://msdn.microsoft.com/vbasic/default.aspx).

Introdução ao Visual Basic 9.0

Para ver o poder dos recursos dessa linguagem em ação, vamos começar com um exemplo do mundo real: o banco de dados da CIA World Factbook. O banco de dados contém diversas informações geografias, econômicas, sociais e políticas sobre os países do mundo. Para o propósito do nosso exemplo, começaremos com um esquema para o nome de cada país e sua capital, área total e população. Representamos esse esquema no Visual Basic 9.0, usando a seguinte classe (pseudocódigo usado por questão de brevidade):

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

Este é um pequeno subconjunto do banco de dados do país que usaremos como exemplo de execução:

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

Dada essa lista, podemos fazer consultar todos os países cuja população seja menor que um milhão, usando a seguinte expressão de consulta:

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

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

Como somente Madagascar possui mais de um milhão de habitantes, o programa acima imprimiria a seguinte lista de nomes de países ao ser compilado e executado:

Palau
Monaco
Belize
	

Vamos examinar o programa para entender os recursos do Visual Basic 9.0 que tornam o ato de escrever tão simples. Primeiro, a declaração de cada expressão que representa Countries usa a nova sintaxe inicializadora de objeto New Country With {..., .Area = 458, ...} para criar uma instância complexa de objeto por meio de uma sintaxe concisa baseada em expressão, semelhante à declaração existente With.

A declaração também ilustra as declarações de variáveis de local digitadas implicitamente , em que o compilador infere o tipo da variável de local Countries da expressão inicializadora no lado direito da declaração. A declaração acima é precisamente equivalente a uma declaração de variável local digitada implicitamente do tipo Country().

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

Para repetir, esta ainda é uma declaração digitada com rigidez; o compilador automaticamente inferiu o tipo do lado direito da declaração de local, e o programador não precisa inserir esse tipo no programa manualmente.

A declaração da variável de local SmallCountries é iniciada com uma expressão de consulta ao estilo SQL para filtrar todos os países que possuem menos de um milhão de habitantes. A semelhança com SQL é intencional, permitindo aos programadores que realmente conhecem SQL começarem a sintaxe da consulta do Visual Basic com muito mais rapidez.

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

Observe que esse exemplo de código apresenta outra aplicação de digitação implícita: o compilador infere o tipo de SmallCountries como IEnumerable(Of Country) baseado no tipo de resultado da expressão de consulta. O compilador traduz a expressão de consulta em si em chamadas para o API habilitado para LINQ, que implementa os operadores de consulta para todos os tipos que implementam IEnumerable(Of T). Nesse caso, a tradução é tão simples quanto o seguinte:

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

A sintaxe expandida depende de expressões lambda, que representam funções embutidas que retornam o resultado de uma expressão. A expressão lambda é convertida em um delegado e passada para a função de extensão Where, que é definida na biblioteca de operador de consulta padrão como uma extensão da interface IEnumerable(Of T).

Agora que vimos alguns dos novos recursos do Visual Basic 9.0, vamos analisar uma visão geral mais detalhada.

Variáveis de local digitadas implicitamente

Em uma declaração de variáveis de local digitada implicitamente, o tipo da variável de local é inferido da expressão inicializadora no lado direito de uma declaração de local. Por exemplo, o compilador infere os tipos de todas as declarações de variáveis a seguir:

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

Assim, elas são exatamente equivalentes às seguintes declarações digitadas explicitamente:

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

Como os tipos de declarações de variáveis de local são inferidos com o novo Option Infer On (o padrão para novos projetos) seja qual for a configuração de Option Strict, o acesso a tais variáveis é sempre early-bound. O programador deve especificar explicitamente a ligação tardia no Visual Basic 9.0, declarando explicitamente variáveis, como as do tipo Object, conforme a seguir:

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

Inferir tipos evita o uso acidental de ligação tardia e, acima disso, permite extensões poderosas de ligação para novos tipos de dados como XML, conforme veremos a seguir.

A variável de controle de loop em uma declaração For...Next ou For Each...Next também pode ser uma variável digitada implicitamente. Quando a variável de controle de loop é especificada, como em For I = 0 To SmallCountries.Count ou em For Each country In smallCountries, o identificador define uma nova variável de local digitada implicitamente, cujo tipo é inferido da expressão de coleção ou do inicializador e tem como escopo o loop inteiro. Com esse aplicativo de inferência de tipo, podemos reescrever o loop que imprime todos os países pequenos, conforme a seguir:

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

O tipo country é inferido para ser Country, o tipo de elemento de SmallCountries.

Inicializadores de objeto e de matriz

No Visual Basic, a declaração With simplifica o acesso aos vários membros de um valor agregado sem especificar a expressão-alvo diversas vezes. Dentro do bloco da declaração With, uma expressão de acesso de membro começando com um ponto é avaliada como se o ponto fosse precedido pela expressão-alvo dessa declaração. Por exemplo, as seguintes declarações inicializam uma nova instância Country e, subseqüentemente, inicializam seus campos com os valores necessários:

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

Os novos inicializadores de objeto no Visual Basic 9.0 são uma forma baseada em expressão de With para criar instâncias de objeto complexas de forma concisa. Usando os inicializadores de objeto, podemos capturar as duas declarações anteriores em uma única declaração de local (digitada implicitamente), conforme a seguir:

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

Esse estilo de inicialização de objeto de expressões é importante em consultas. Normalmente, uma consulta se parece com uma declaração de objeto inicializada por uma cláusula Select no lado direito do sinal de igual. Como a cláusula Select retorna uma expressão, devemos poder inicializar o objeto inteiro com uma única expressão.

Como vimos, os inicializadores de objeto também são convenientes para criar coleções de objetos complexos. As matrizes podem ser inicializadas e os tipos de elementos inferidos usando uma expressão inicializadora de matriz . Por exemplo, dada a declaração para cidades como a classe,

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

podemos criar uma matriz de capitais para nossos países de exemplo da seguinte maneira:

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

	

Tipos anônimos

Com freqüência, queremos apenas remover, ou projetar, alguns membros de um tipo como resultado de uma consulta. Por exemplo, talvez quiséssemos saber apenas o Name e o Country de todas as capitais, usando as colunas Latitude e Longitude nos dados de origem para identificar os trópicos, mas projetando essas colunas no resultado. No Visual Basic 9.0, fazemos isso criando uma nova instância de objeto, sem nomear o tipo, para cada cidade C cuja latitude esteja entre o trópico de Câncer e o trópico de Capricórnio.

Const TropicOfCancer = 23.5
Const TropicOfCapricorn = -23.5

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

O tipo inferido da variável de local Tropical é uma coleção de instâncias de um tipo anônimo, ou seja (usando pseudocódigo) IEnumerable(Of { Name As String, Country As String }). O compilador do Visual Basic criará uma classe implícita, por exemplo _Name_As_String_Country_As_String_, cujos nomes de membros e tipos são inferidos do inicializador de objeto, conforme se segue:

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

Dentro do mesmo programa, o compilador mesclará tipos anônimos idênticos. Dois inicializadores de objeto anônimos que especificam uma seqüência de propriedades de mesmo nome e tipo na mesma ordem produzirá instâncias do mesmo tipo anônimo. Externamente, os tipos anônimos gerados pelo Visual Basic são apagados como Object, que permite ao compilador passar uniformemente tipos anônimos como argumentos e resultados de funções.

Como os tipos anônimos são normalmente usados para projetar membros de um tipo existente, o Visual Basic 9.0 permite a notação de projeção abreviada New With { city.Name, city.Country } para encurtar a forma longa New With { .Name = city.Name, .Country = city.Country }. Quando usados na expressão de resultado de uma compreensão de consulta, podemos abreviar ainda mais os inicializadores de projeção, da seguinte maneira:

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

Observe que as duas formas abreviadas são idênticas em significado à forma longa acima.

Suporte de nível profundo a XML

LINQ para XML é uma nova API de programação XML na memória projetada especificamente para aproveitar os últimos recursos do .NET Framework como a estrutura Language-Integrated Query. Assim como compreensão da consulta adiciona uma sintaxe conveniente e familiar aos operadores de consulta subjacentes padrão do .NET Framework, o Visual Basic 9.0 fornece suporte de nível profundo a LINQ para XML por meio de literais XML e propriedades XML .

Para ilustrar os literais XML, vamos fazer uma consulta nas fontes de dados relacionais basicamente simples Countries e Capitals para construir um modelo hierárquico XML que aninhe a capital de cada país como um elemento filho e calcule a densidade populacional como um atributo.

Para encontrar a capital de um determinado país, fazemos uma combinação do nome-membro de cada país com o país-membro de cada cidade. Dado um país e sua capital, podemos facilmente construir o fragmento XML preenchendo as brechas da expressão incorporada com valores calculados. Escreveríamos uma "brecha" para uma expressão do Visual Basic com a sintaxe reminiscente de ASP, como em Name=<%= country.Name %> ou <Name><%= city.Name %></Name>. Esta é a nossa consulta que combina literais XML com compreensão de consulta:

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

Observe que o tipo XElement poderia ser omitido da declaração e, nesse caso, seria inferida, assim como qualquer outra declaração de local.

Nessa declaração, o resultado da consulta Select deve ser substituído dentro do elemento <Countries>. Assim, a consulta Select é o conteúdo da primeira 'brecha', delimitada pelas conhecidas marcas de estilo ASP <%= e %> dentro de <Countries>. Como o resultado de uma consulta Select é uma expressão, e os literais XML são expressões, é natural aninhar outro literal XML no próprio Select. Esse literal aninhado em si contém "brechas" de atributo aninhado para Country.Name e a taxa de densidade populacional calculada Country.Population/Country.Area, e "brechas" de elemento aninhado para o nome e as coordenadas da capital.

Quando compilada e executada, a consulta acima retornará o seguinte documento XML (ligeiramente reformatado da bela impressão IDE padrão para economizar espaço).

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

O Visual Basic 9.0 compila literais XML em objetos System.Xml.Linq normais, garantindo a total operabilidade entre o Visual Basic e outras linguagens que usam LINQ para XML. Para a nossa consulta de exemplo, o código produzido pelo compilador (se pudéssemos ver) seria:

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

Além de construir XML, o Visual Basic 9.0 também simplifica o acesso a estruturas XML via propriedades XML, quer dizer, os identificadores no código do Visual Basic são ligados em tempo de execução a atributos e elementos XML correspondentes. Por exemplo, podemos imprimir a densidade populacional de todos os países do exemplo conforme a seguir:

  • Use o eixo filho countriesWithCapital.<Country> para obter todos os elementos "Country " da estrutura XML countriesWithCapital.

  • Use o eixo de atributo *country.@Density* para obter o atributo "Density" do elemento Country.

  • Use o eixo descendente country...<Latitude> — escrito literalmente como três pontos no código fonte —para obter todos os filhos "Latitude" do elemento Country, independentemente do nível em que ocorrem na hierarquia.

  • Use a propriedade de extensão .Value em IEnumerable(Of XElement) para selecionar o valor do primeiro elemento da seqüência resultante ou o indexador de extensão (i) para selecionar o elemento i-th.

Colocando todos esses recursos juntos, o código pode ser drasticamente condensado em simplificado:

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

O compilador sabe usar a ligação tardia em objetos normais quando a expressão-alvo de uma declaração, atribuição ou inicialização é do tipo Object , e não de um tipo mais específico. Da mesma forma, o compilador sabe usar ligação em XML quando a expressão-alvo é do tipo, ou da coleção de, XElement, XDocument ou XAttribute.

Como resultado da ligação tardia em XML, o compilador traduz da seguinte maneira:

  • A expressão do eixo filho countriesWithCapital.<Country> é traduzida na chamada LINQ para XML não processada countriesWithCapital.Elements("Country"), que retorna a coleção de todos os elementos filho chamados "Country" do elemento Country.

  • A expressão do eixo de atributo country.@Density é traduzida em Country.Attribute("Density").Value, que retorna o único atributo filho chamado "Density" de Country;.

  • A expressão do eixo descendente country...<Latitude> é traduzida na chamada LINQ para XML não processada country.Descendants(“Latitude?), que retorna a cole�ção de todos os elementos chamados em qualquer nível abaixo de país country.

Compreensão de consulta

Um operador de consulta é aquele, como Select, Order By ou Where, que pode ser aplicado a um conjunto de valores na coleção inteira ao mesmo tempo.

Uma expressão de consulta é aquela que se aplica a uma série de operadores de consulta em uma coleção específica. Por exemplo, a seguinte expressão de consulta adota um conjunto de países e retorna os nomes de todos aqueles que possuem menos de um milhão de habitantes.

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

A sintaxe da expressão de consulta é projetada para ser razoavelmente semelhante à sintaxe relacional padrão SQL, com a intenção de permitir que qualquer pessoa familiarizada com SQL possa usar as expressões de consulta com muito pouca instrução. Entretanto, a sintaxe não se restringe a SQL e as expressões de consulta não pretendem ser uma tradução de SQL no Visual Basic. Como a linguagem SQL foi projetada em torno de um modelo totalmente relacional, alguns de seus idiomas não funcionam muito bem em um sistema de tipos que permite, e até adota, a hierarquia. Além disso, alguns elementos SQL sintáticos e semânticos entram em conflito, ou não se misturam bem com, a sintaxe ou a semântica existente do Visual Basic. Assim, embora as expressões de consulta devam parecer familiares para quem conhece SQL, algumas diferenças deverão ser aprendidas.

As expressões de consulta são traduzidas em chamadas para os operadores de seqüência subjacentes em tipos consultáveis específicos como o tipo-fonte na cláusula From. Como os operadores de seqüência, em geral, são definidos como métodos de extensão no tipo-fonte, eles estão ligados a qualquer operador de seqüência no escopo. Isso implica que, ao importar uma implementação específica, a sintaxe da expressão de consulta pode ser ligada novamente a diferentes APIs habilitadas para LINQ. É assim que as expressões de consulta podem ser ligadas novamente a uma implementação que sua LINQ para SQL ou LINQ para objetos (um mecanismo de execução de consulta de local que executa a consulta na memória).

Alguns operadores de consulta, como From, Select e Group By, apresentam um tipo especial de variável de local chamado variável de alcance. Por padrão, uma variável de alcance é compreendida pelo operador de introdução até um operador que oculta a variável de alcance e representa uma propriedade ou coluna da linha individual em uma coleção quando a consulta é avaliada. Por exemplo, na seguinte consulta:

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

O operador From apresenta uma variável de alcance country digitada como "Country". O operador de consulta seguinte Where se refere à variável de alcance country para representar cada cliente individual na expressão do filtro country.Population < 1000000.

Alguns operadores de consulta, como Distinct, não usam nem alteram a variável de controle. Outros operadores de consulta, como Select, ocultam as variáveis de alcance atuais no escopo e apresentam novas. Por exemplo, na consulta:

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

O operador de consulta Order By tem acesso apenas às variáveis de alcance Name e Pop apresentada pelo operador Select; se o operador Order By tentasse se referir a Country, ocorreria um erro de tempo de compilação.

Se uma consulta terminar sem um operador Select, o tipo de elemento resultante da coleção será como se uma projeção Select fosse apresentada com todas as variáveis de controle no escopo:

Dim countriesWithCapital = From country In Countries, city In Capitals _
                           Where country.Name = city.Country
The inferred type for this local declaration is (in pseudo-code to represent an anonymous type) 
IEnumerable(Of { Country As Country, City As City }).

	

Em que as expressões de consulta diferem de SQL: dados hierárquicos e composicionalidade

As expressões de consulta no Visual Basic 9.0 são totalmente composicionais, o que significa que podem ser aninhadas ou criadas arbitrariamente acrescentando uma consulta com operadores de consulta adicionais. A composicionalidade facilita o entendimento de uma consulta grande, simplesmente ao compreendermos cada subexpressão individual isolada, e facilita o acompanhamento da semântica e dos tipos que fluem em cada operador de consulta. Entretanto, a composicionalidade como princípio de criação produz uma experiência bastante diferente ao escrever uma consulta em SQL, que analisa as consultas como um bloco monolítico.

Além disso, as APIs habilitadas para LINQ tendem a implementar os operadores de seqüência com execução retardada. Execução retardada significa que a consulta não é avaliada enquanto os resultados não são enumerados. Em LINQ para SQL, isso significa que a consulta não é transmitida remotamente para SQL enquanto os resultados não são solicitados. Isso quer dizer que separar as consultas em várias declarações não significa que o banco de dados é atingido várias vezes. Como resultado, o que normalmente seria uma consulta aninhada em SQL se torna uma consulta composicional em LINQ.

Um motivo para a falha de composicionalidade do SQL é que o modelo de dados relacional subjacente em si não é composicional. Por exemplo, as tabelas talvez não contenham subtabelas; em outras palavras, todas as tabelas devem ser simples. Assim, em vez de dividir expressões complexas em unidades menores, os programadores de SQL escrevem expressões monolíticas cujos resultados são tabelas simples, adequadas ao modelo de dados SQL. Como o Visual Basic se baseia no sistema de tipos CLR, não há restrições para os tipos que podem aparecer como componentes de outros tipos. Salvo as regras de tipos estáticos, não há restrições para os tipos de expressões que podem aparecer como componentes de outras expressões. Portanto, não apenas entradas de linhas, objetos e XML, mas também do Active Directory, de arquivos, registro, e assim por diante, são cidadãos de primeira classe em fontes e em resultados de consulta.

Operadores de consulta

Quem conhece a implementação de SQL reconhecerá, nos operadores de seqüência .NET subjacentes, muitos operadores de álgebra relacional composicional, como projeção, seleção, produto cruzado, agrupamento e classificação, que representam planos dentro do processador de consulta.

  • O operador From apresenta uma ou mais variáveis de alcance e especifica uma coleção para consulta ou calcula um valor para essas variáveis.

  • O operador Select especifica a forma da coleção de saída.

  • Os operadores Where e Distinct restringem os valores da coleção.

  • O operador Order By impõe uma ordem na coleção.

  • Os operadores Skip, Skip While, Take e Take While retornam um subconjunto de uma coleção com base na ordem ou na condição.

  • Os operadores Union, Union All, Except e Intersect adotam duas seleções e produzem uma única coleção.

  • The Group By operator groups the collection based on one or more keys.

  • The Avg, Sum, Count, Min, and Max operators aggregate a collection and produce a value.

  • The Any and All operators aggregate a collection and return a Boolean value based on a condition.

  • The Join operator takes two collections and produces a single collection based on matching keys derived from the elements.

  • The Group Join operator performs a grouped join of two collections based on matching keys extracted from the elements.

The full syntax for all of the operators can be found in the full language specification. However, for illustration purposes, the following finds the capitals of each country and orders the country names by the latitude of the capital:

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

For queries that compute a scalar value based on a collection, the Aggregate operator works over the collection. The following query finds the number of small countries and computes their average density in one statement:

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

Aggregate functions appear most often in combination with partitioning the source collection. For example, we can group all countries by whether they are tropical and then aggregate the count of each group. To do so, the aggregate operators can be used in conjunction with the Group By and Group Join clauses. In the example below, the helper function, IsTropical encapsulates the test whether a City has a tropical climate:

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

Given this helper function, we use exactly the same aggregation as above, but first partition the input collection of Country and Capitalpairs into groups for which Country.IsTropical is the same. In this case there are two such groups: one that contains the tropical countries Palau, Belize, and Madagascar; and another that contains the non-tropical country Monaco.

Key

Country

City

Country.IsTropical() = True

Palau

Koror

 

Belize

Belmopan

 

Madagascar

Antanarivo

Country.IsTropical() = False

Monaco

Monaco

Then, we aggregate the values in these groups by computing the total count and average density. The result type is now a collection of pairs of Total As Integer and Density As Double:

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

The above query hides considerable complexity. The query below produces the same results using lambda expressions and extension methods to express the query with method call syntax.

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

Métodos de extensão e expressões lambda

Boa parte do poder subjacente a infra-estrutura da consulta padrão do .NET Framework vem dos métodos de extensão e das expressões lambda . Os métodos de extensão são métodos compartilhados com atributos personalizados que permitem ser invocados com sintaxe de método de instância. A maioria dos métodos de extensão possuem assinaturas semelhantes. O primeiro argumento é a instância contra a qual o método é aplicado e o segundo é o predicado a ser aplicado. Por exemplo, o método Where, ao qual a cláusula Where é traduzida, possui a assinatura:

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

Vários operadores de consulta padrão, como Where, Select, SelectMany e outros, são definidos como métodos de extensão que adotam delegados do tipo Func(Of S,T) como argumentos que o compilador abstrai da exigência de produzir os delegados que representam o predicado. O compilador cria fechamentos, delegados que capturam o contexto adjacente e o transmitem para a chamada de método subjacente. Por exemplo, com a seguinte consulta, o compilador gera expressões lambda que representam os delegados a serem transmitidos às funções Select e Where.

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

O compilador gera duas expressões lambda para a projeção e o predicado respectivamente.

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

E a consulta acima é traduzida em chamadas de método para a estrutura de consulta padrão, transmitindo a fonte e a expressão lambda para serem aplicadas como argumentos:

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

Tipos anuláveis

Os bancos de dados relacionais apresentam uma semântica para valores anuláveis que freqüentemente são inconsistentes com as linguagens de programação comuns e, em geral, desconhecidos pelos programadores. Em aplicativos de dados intensivos, é essencial que os programas manipulem essa semântica clara e corretamente. Reconhecendo essa necessidade, com o .NET Frameworks 2.0, o CLR adicionou suporte em tempo de execução para nulabilidade usando o tipo genérico Nullable(Of T As Structure). Usando esse tipo, podemos declarar versões anuláveis de tipos de valor, como Integer, Boolean, Date e assim por diante. Por motivos que se tornarão aparentes, a sintaxe do Visual Basic para tipos anuláveis é T?.

Por exemplo, como nem todos os países são independentes, podemos adicionar um novo membro à classe Country que representa sua data de independência, se aplicável.

Partial Class Country
  Public Property Independence As Date?
End Class 
	

A data de independência de Palau é #10/1/1994#, mas as Ilhas Virgens Britânicas são um território dependente do Reino Unido e, portanto, sua data de independência é Nothing.

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

Dim virginIslands = _
  New Country With { _
    .Name = "Virgin Islands", _
    .Area = 150, _
    .Population = 13195, _
    .Independence = Nothing }

	

O Visual Basic 9.0 oferecerá suporte para lógica de três valores e aritmética de propagação nula em valores anuláveis, o que significa que, se um dos operandos de uma operação aritmética, de comparação, de lógica ou bit a bit, de deslocamento, de seqüência de caracteres ou tipo for Nothing, o resultado será Nothing. Se os dois operandos forem valores apropriados, a operação será realizada nos valores subjacentes dos operandos e o resultado será convertido para anulável.

Como Palau.Independence e VirginIslands.Independence têm o tipo Date?, o compilador usará aritmética de propagação nula para as subtrações abaixo e, assim, o tipo inferido para a declaração de local PLength e VILength será TimeSpan? para ambos.

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

O valor de PLength é 3980.00:00:00 porque nenhum dos operandos é Nothing. Entretanto, como o valor de VirginIslands.Independence é Nothing, o resultado é novamente do tipo TimeSpan?, mas o valor de VILength será Nothing por causa do tipo de propagação nula.

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

Assim como no SQL, os operadores de comparação farão a propagação nula, e os operadores lógicos usarão a lógica de três valores. Nas declarações If e While, Nothing é interpretado como False; assim, no seguinte de trecho de código, a ramificação Else é adotada:

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

Observe que sob a lógica de três valores, as verificações de igualdade X = Nothing e Nothing = X são sempre avaliadas como Nothing; para verificar se X é Nothing, devemos usar a comparação de lógica de dois valores X Is Nothing ou Nothing Is X.

Delegados relaxados

Ao criar um delegado usando AddressOf ou Handles no Visual Basic 8.0, um dos métodos usados para fazer a ligação com o identificador de delegado deve corresponder exatamente à assinatura do tipo de delegado. No exemplo abaixo, a assinatura da sub-rotina OnClick deve corresponder exatamente à assinatura do delegado manipulador do evento Delegate Sub EventHandler(sender As Object, e As EventArgs), que é declarado nos bastidores no tipo Button:

Dim WithEvents btn As New Button()

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

Entretanto, ao invocar funções e sub-rotinas não-delegadas, o Visual Basic não exige que os argumentos reais correspondam exatamente a um dos métodos que estamos tentando invocar. Como mostra o fragmento a seguir, podemos realmente invocar a sub-rotina OnClick usando um argumento real do tipo Button e do tipo MouseEventArgs, que são subtipos dos parâmetros formais Object e EventArgs, respectivamente:

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

Entretanto, supondo que pudéssemos definir uma sub-rotina RelaxedOnClick que adotasse dois parâmetros Object e, então, pudéssemos chamá-la com argumentos reais do tipo Object e EventArgs

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

No Visual Basic 9.0, a ligação a delegados é relaxada para ser consistente com a invocação de método. Quer dizer, se for possível invocar uma função ou sub-rotina com argumentos reais que correspondam exatamente com o parâmetro formal e retornem tipos de um delegado, poderemos ligar essa função ou sub-rotina ao delegado. Em outras palavras, a ligação e definição de delegado seguirá a mesma lógica de resolução de sobrecarga que a invocação de método.

Isso implica que agora, no Visual Basic 9.0, é possível ligar uma sub-rotina RelaxedOnClick que adota dois parâmetros Object ao evento Click de um Button:

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

Os dois argumentos para o manipulador de evento, sender e EventArgs, raramente importam. Em vez disso, o manipulador acessa o estado do controle no qual o evento é registrado diretamente e ignora seus dois argumentos. Para dar suporte a esse caso comum, os delegados podem ser relaxados para não adotarem nenhum argumento, se não resultar nenhuma ambigüidade. Em outras palavras, podemos simplesmente escrever o seguinte:

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

Sabe-se que o relaxamento de delegado também é aplicável ao construir delegados usando AddressOf ou uma expressão de criação de delegado, mesmo quando o grupo de métodos é uma chamada de ligação tardia:

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

Conclusão

O Visual Basic 9.0 unifica o acesso aos dados independentemente de sua origem em banco de dados relacional, documentos XML ou gráficos de objetos arbitrários, mesmo que persistentes ou armazenados na memória. A unificação consiste em estilos, técnicas, ferramentas e padrões de programação. A sintaxe especialmente flexível do Visual Basic torna mais fácil a tarefa de adicionar extensões como literais XML e expressões de consulta semelhantes a SQL amplamente na linguagem. Isso reduz significativamente a "área de superfície" das novas APIs .NET Language-Integrated Query, aumenta a detectabilidade de recursos de acesso a dados por meio do IntelliSense e de marcas inteligentes e aprimora imensamente a depuração e verificação de tempo de compilação, destacando sintaxes estrangeiras dos dados da seqüência de caracteres no Visual Basic.

Além disso, recursos como inferência de tipo, inicializadores de objeto e delegados relaxados reduzem enormemente a redundância de código e o número de exceções às regras que os programadores precisam aprender e memorizar ou consultar, sem afetar o desempenho.

Embora a lista de novos recursos no Visual Basic 9.0 possa parecer longa, esperamos que os temas descritos anteriormente o convençam de que é coerente, oportuno e dedicado tornar o Visual Basic a melhor linguagem de programação do mundo. Esperamos que sua imaginação seja estimulada, também, e que você se junte a nós e reconheça que este é a apenas o começo de outras grandes realizações que estão para acontecer.

 

© 2007 Microsoft Corporation. Todos os direitos reservados. Termos de uso.