Arquivos de origem e namespaces

Um programa Visual Basic consiste em um ou mais arquivos de origem. Quando um programa é compilado, todos os arquivos de origem são processados juntos; Assim, os ficheiros-fonte podem depender uns dos outros, possivelmente de forma circular, sem qualquer requisito de declaração transitária. A ordem textual das declarações no texto do programa geralmente não tem importância.

Um arquivo de origem consiste em um conjunto opcional de instruções de opção, instruções de importação e atributos, que são seguidos por um corpo de namespace. Os atributos, que devem ter o Assembly modificador ou , aplicam-se ao assembly ou Module módulo .NET produzido pela compilação. O corpo do arquivo de origem funciona como uma declaração de namespace implícita para o namespace global, o que significa que todas as declarações no nível superior de um arquivo de origem são colocadas no namespace global. Por exemplo:

FileA.vb:

Class A
End Class

FileB.vb:

Class B
End Class

Os dois arquivos de origem contribuem para o namespace global, neste caso declarando duas classes com os nomes A totalmente qualificados e B. Como os dois arquivos de origem contribuem para o mesmo espaço de declaração, teria sido um erro se cada um contivesse uma declaração de um membro com o mesmo nome.

Nota. O ambiente de compilação pode substituir as declarações de namespace nas quais um arquivo de origem é colocado implicitamente.

Exceto onde observado, instruções dentro de um programa Visual Basic podem ser encerradas por um terminador de linha ou por dois pontos.

Start
    : OptionStatement* ImportsStatement* AttributesStatement* NamespaceMemberDeclaration*
    ;

StatementTerminator
    : LineTerminator
    | ':'
    ;

AttributesStatement
    : Attributes StatementTerminator
    ;

Arranque e Terminação do Programa

A inicialização do programa ocorre quando o ambiente de execução executa um método designado, que é referido como o ponto de entrada do programa. Este método de ponto de entrada, que deve ser sempre nomeado Main, deve ser compartilhado, não pode estar contido em um tipo genérico, não pode ter o modificador assíncrono e deve ter uma das seguintes assinaturas:

Sub Main()
Sub Main(args() As String)
Function Main() As Integer
Function Main(args() As String) As Integer

A acessibilidade do método do ponto de entrada é irrelevante. Se um programa contiver mais de um ponto de entrada adequado, o ambiente de compilação deve designar um como ponto de entrada. Caso contrário, ocorrerá um erro em tempo de compilação. O ambiente de compilação também pode criar um método de ponto de entrada, se não existir.

Quando um programa começa, se o ponto de entrada tiver um parâmetro, o argumento fornecido pelo ambiente de execução contém os argumentos de linha de comando para o programa representado como strings. Se o ponto de entrada tiver um tipo de retorno de Integer, o valor retornado da função será retornado ao ambiente de execução como resultado do programa.

Em todos os outros aspetos, os métodos de ponto de entrada comportam-se da mesma maneira que outros métodos. Quando a execução deixa a invocação do método de ponto de entrada feita pelo ambiente de execução, o programa é encerrado.

Opções de compilação

Um arquivo de origem pode especificar opções de compilação no código-fonte usando instruções de opção.

OptionStatement
    : OptionExplicitStatement
    | OptionStrictStatement
    | OptionCompareStatement
    | OptionInferStatement
    ;

Uma Option instrução se aplica somente ao arquivo de origem no qual ela aparece, e apenas uma de cada tipo de instrução pode aparecer em um arquivo de Option origem. Por exemplo:

Option Strict On
Option Compare Text
Option Strict Off    ' Not allowed, Option Strict is already specified.
Option Compare Text  ' Not allowed, Option Compare is already specified.

Há quatro opções de compilação: semântica de tipo estrito, semântica de declaração explícita, semântica de comparação e semântica de inferência de tipo de variável local. Se um arquivo de origem não incluir uma instrução específica Option , o ambiente de compilação determinará qual conjunto específico de semântica será usado. Há também uma quinta opção de compilação, verificações de estouro de inteiro, que só podem ser especificadas através do ambiente de compilação.

Declaração explícita da opção

A Option Explicit instrução determina se as variáveis locais podem ser declaradas implicitamente. As palavras-chave On ou Off podem seguir a instrução, se nenhuma for especificada, o padrão é On. Se nenhuma instrução for especificada em um arquivo, o ambiente de compilação determinará qual será usada.

OptionExplicitStatement
    : 'Option' 'Explicit' OnOff? StatementTerminator
    ;

OnOff
    : 'On' | 'Off'
    ;

Nota. Explicit e Off não são palavras reservadas.

Option Explicit Off

Module Test
    Sub Main()
        x = 5 ' Valid because Option Explicit is off.
    End Sub
End Module

Neste exemplo, a variável x local é implicitamente declarada atribuindo-a. O tipo de x é Object.

Declaração Estrita de Opção

A Option Strict instrução determina se as conversões e operações em Object são regidas por semântica de tipo estrita ou permissiva e se os tipos são digitados implicitamente como Object se nenhuma As cláusula fosse especificada. A instrução pode ser seguida pelas palavras-chave On ou Off, se nenhuma for especificada, o padrão é On. Se nenhuma instrução for especificada em um arquivo, o ambiente de compilação determinará qual será usada.

OptionStrictStatement
    : 'Option' 'Strict' OnOff? StatementTerminator
    ;

Nota. Strict e Off não são palavras reservadas.

Option Strict On

Module Test
    Sub Main()
        Dim x ' Error, no type specified.
        Dim o As Object
        Dim b As Byte = o ' Error, narrowing conversion.

        o.F() ' Error, late binding disallowed.
        o = o + 1 ' Error, addition is not defined on Object.
    End Sub
End Module

Sob semântica estrita, o seguinte não é permitido:

  • Estreitamento de conversões sem um operador de elenco explícito.

  • Vinculação tardia.

  • Operações de tipo Object diferente de TypeOf...Is, Ise IsNot.

  • Omitir a As cláusula em uma declaração que não tem um tipo inferido.

Declaração de comparação de opções

A Option Compare instrução determina a semântica das comparações de cadeia de caracteres. As comparações de cadeia de caracteres são realizadas usando comparações binárias (nas quais o valor binário Unicode de cada caractere é comparado) ou comparações de texto (nas quais o significado lexical de cada caractere é comparado usando a cultura atual). Se nenhuma instrução for especificada em um arquivo, o ambiente de compilação controlará qual tipo de comparação será usado.

OptionCompareStatement
    : 'Option' 'Compare' CompareOption StatementTerminator
    ;

CompareOption
    : 'Binary' | 'Text'
    ;

Nota.Compare, Binarye Text não são palavras reservadas.

Option Compare Text

Module Test
    Sub Main()
        Console.WriteLine("a" = "A")    ' Prints True.
    End Sub
End Module

Nesse caso, a comparação de cadeia de caracteres é feita usando uma comparação de texto que ignora as diferenças de maiúsculas e minúsculas. Se Option Compare Binary tivesse sido especificado, então isso teria impresso False.

Verificações de estouro de número inteiro

As operações inteiras podem ser verificadas ou não verificadas quanto a condições de estouro em tempo de execução. Se as condições de estouro forem verificadas e uma operação inteira estourar, uma System.OverflowException exceção será lançada. Se as condições de estouro não forem verificadas, os estouros de operação inteira não lançarão uma exceção. O ambiente de compilação determina se essa opção está ativada ou desativada.

Declaração de inferência de opção

A Option Infer instrução determina se as declarações de variáveis locais que não As têm cláusula têm um tipo ou uso Objectinferido. A instrução pode ser seguida pelas palavras-chave On ou Off, se nenhuma for especificada, o padrão é On. Se nenhuma instrução for especificada em um arquivo, o ambiente de compilação determinará qual será usada.

OptionInferStatement
    : 'Option' 'Infer' OnOff? StatementTerminator
    ;

Nota. Infer e Off não são palavras reservadas.

Option Infer On

Module Test
    Sub Main()
        ' The type of x is Integer
        Dim x = 10

        ' The type of y is String
        Dim y = "abc"
    End Sub
End Module

Declaração de Importação

Imports as instruções importam os nomes das entidades para um arquivo de origem, permitindo que os nomes sejam referenciados sem qualificação, ou importam um namespace para uso em expressões XML.

ImportsStatement
    : 'Imports' ImportsClauses StatementTerminator
    ;

ImportsClauses
    : ImportsClause ( Comma ImportsClause )*
    ;

ImportsClause
    : AliasImportsClause
    | MembersImportsClause
    | XMLNamespaceImportsClause
    ;

Dentro das declarações de membro em um arquivo de origem que contém uma Imports instrução, os tipos contidos no namespace fornecido podem ser referenciados diretamente, como visto no exemplo a seguir:

Imports N1.N2

Namespace N1.N2
    Class A
    End Class
End Namespace 

Namespace N3
    Class B
        Inherits A
    End Class
End Namespace

Aqui, dentro do arquivo de origem, os membros do tipo de namespace N1.N2 estão diretamente disponíveis e, portanto, a classe N3.B deriva da classe N1.N2.A.

Imports as declarações devem figurar depois de quaisquer Option declarações, mas antes de quaisquer declarações de tipo. O ambiente de compilação também pode definir instruções implícitas Imports .

Imports As instruções disponibilizam nomes em um arquivo de origem, mas não declaram nada no espaço de declaração do namespace global. O escopo dos nomes importados por uma Imports instrução se estende sobre as declarações de membro do namespace contidas no arquivo de origem. O escopo de uma Imports declaração especificamente não inclui outras Imports declarações, nem inclui outros arquivos de origem. Imports as declarações não podem referir-se umas às outras.

Neste exemplo, a última Imports instrução está errada porque não é afetada pelo primeiro alias de importação.

Imports R1 = N1 ' OK.
Imports R2 = N1.N2 ' OK.
Imports R3 = R1.N2 ' Error: Can't refer to R1.

Namespace N1.N2
End Namespace

Nota. Os nomes de namespace ou tipo que aparecem nas Imports instruções são sempre tratados como se fossem totalmente qualificados. Ou seja, o identificador mais à esquerda em um namespace ou nome de tipo sempre é resolvido no namespace global e o restante da resolução prossegue de acordo com as regras normais de resolução de nomes. Este é o único lugar na língua que aplica tal regra; A regra garante que um nome não possa ser completamente escondido da qualificação. Sem a regra, se um nome no namespace global estivesse oculto em um determinado arquivo de origem, seria impossível especificar quaisquer nomes desse namespace de forma qualificada.

Neste exemplo, a Imports instrução sempre se refere ao namespace global System e não à classe no arquivo de origem.

Imports System   ' Imports the namespace, not the class.

Class System
End Class

Importar aliases

Um alias de importação define um alias para um namespace ou tipo.

AliasImportsClause
    : Identifier Equals TypeName
    ;
Imports A = N1.N2.A

Namespace N1.N2
    Class A
    End Class
End Namespace

Namespace N3
    Class B
        Inherits A
    End Class
End Namespace

Aqui, dentro do arquivo de origem, é um alias para N1.N2.A, e, portanto, A a classe N3.B deriva da classe N1.N2.A. O mesmo efeito pode ser obtido criando um alias R para N1.N2 e, em seguida, fazendo referência a R.A:

Imports R = N1.N2

Namespace N3
    Class B
        Inherits R.A
    End Class
End Namespace

O identificador de um alias de importação deve ser exclusivo dentro do espaço de declaração do namespace global (não apenas a declaração de namespace global no arquivo de origem no qual o alias de importação está definido), mesmo que não declare um nome no espaço de declaração do namespace global.

Nota. As declarações em um módulo não introduzem nomes no espaço de declaração que contém. Assim, é válido que uma declaração em um módulo tenha o mesmo nome que um alias de importação, mesmo que o nome da declaração esteja acessível no espaço de declaração que contém.

' Error: Alias A conflicts with typename A
Imports A = N3.A

Class A
End Class

Namespace N3
    Class A
    End Class
End Namespace

Aqui, o namespace global já contém um membro A, portanto, é um erro para um alias de importação usar esse identificador. Da mesma forma, é um erro para dois ou mais aliases de importação no mesmo arquivo de origem declarar aliases pelo mesmo nome.

Um alias de importação pode criar um alias para qualquer namespace ou tipo. Acessar um namespace ou tipo por meio de um alias produz exatamente o mesmo resultado que acessar o namespace ou tipo por meio de seu nome declarado.

Imports R1 = N1
Imports R2 = N1.N2

Namespace N1.N2
    Class A
    End Class
End Namespace

Namespace N3
    Class B
        Private a As N1.N2.A
        Private b As R1.N2.A
        Private c As R2.A
    End Class
End Namespace

Aqui, os nomes N1.N2.A, R1.N2.Ae R2.A são equivalentes, e todos se referem à classe cujo nome totalmente qualificado é N1.N2.A.

A importação especifica o nome exato do namespace ou tipo para o qual está criando um alias. Este deve ser o nome exato totalmente qualificado desse namespace ou tipo: ele não usa as regras normais para resolução de nome qualificado (que, por exemplo, permitem o acesso aos membros de uma classe base por meio de uma classe derivada).

Se um alias de importação apontar para um tipo ou namespace que não pode ser resolvido por essas regras, a instrução import será ignorada (e o compilador dará um aviso).

Além disso, a referência não pode ser a um tipo genérico aberto -- todos os tipos genéricos devem ter argumentos de tipo válidos fornecidos, e todos os argumentos de tipo devem ser resolvíveis pelas regras acima. Qualquer ligação incorreta de um tipo genérico é um erro.

Imports A = G              ' error: since G is an open generic type
Imports B = G(Of Integer)  ' okay
Imports C = Derived.Nested ' warning: Derived.Nested isn't itself a type
Imports D = G(Of Derived.Nested) ' error: Derived.Nested isn't found

Class G(Of T) : End Class

Class Base
    Class Nested : End Class
End Class

Class Derived : Inherits Base
End Class

Module Module1
    Sub Main()
        Dim x As C               ' error: "C" wasn't succesfully defined
        Dim y As Derived.Nested  ' okay
    End Sub
End Module

As declarações no arquivo de origem podem sombrear o nome do alias de importação.

Imports R = N1.N2

Namespace N1.N2
    Class A
    End Class
End Namespace

Namespace N3
    Class R
    End Class

    Class B
        Inherits R.A  ' Error, R has no member A
    End Class
End Namespace

No exemplo anterior, a referência na R.A declaração de B causa um erro porque R se refere a N3.R, não N1.N2.

Um alias de importação disponibiliza um alias em um arquivo de origem específico, mas não contribui com novos membros para o espaço de declaração subjacente. Em outras palavras, um alias de importação não é transitivo, mas afeta apenas o arquivo de origem no qual ele ocorre.

File1.vb:

Imports R = N1.N2

Namespace N1.N2
    Class A
    End Class
End Namespace

File2.vb:

Class B
    Inherits R.A ' Error, R unknown.
End Class

No exemplo acima, como o escopo do alias de importação que introduz R se estende apenas às declarações no arquivo de origem no qual ele está contido, R é desconhecido no segundo arquivo de origem.

Importações de namespace

Uma importação de namespace importa todos os membros de um namespace ou tipo, permitindo que o identificador de cada membro do namespace ou tipo seja usado sem qualificação. No caso de tipos, uma importação de namespace só permite acesso aos membros compartilhados do tipo sem exigir a qualificação do nome da classe. Em particular, permite que os membros dos tipos enumerados sejam usados sem qualificação.

MembersImportsClause
    : TypeName
    ;

Por exemplo:

Imports Colors

Enum Colors
    Red
    Green
    Blue
End Enum

Module M1
    Sub Main()
        Dim c As Colors = Red
    End Sub
End Module

Ao contrário de um alias de importação, uma importação de namespace não tem restrições sobre os nomes que importa e pode importar namespaces e tipos cujos identificadores já estão declarados dentro do namespace global. Os nomes importados por uma importação regular são sombreados por aliases de importação e declarações no arquivo de origem.

No exemplo a seguir, A refere-se a N3.A declarações de membro no namespace em vez de N1.N2.A dentro dela N3 .

Imports N1.N2

Namespace N1.N2
    Class A
    End Class
End Namespace

Namespace N3
    Class A
    End Class

    Class B
        Inherits A
    End Class
End Namespace

Quando mais de um namespace importado contém membros com o mesmo nome (e esse nome não é sombreado por um alias ou declaração de importação), uma referência a esse nome é ambígua e causa um erro em tempo de compilação.

Imports N1
Imports N2

Namespace N1
    Class A
    End Class
End Namespace 

Namespace N2
    Class A
    End Class
End Namespace 

Namespace N3
    Class B
        Inherits A ' Error, A is ambiguous.
    End Class
End Namespace

No exemplo acima, ambos N1 e N2 contêm um membro A. Como N3 importa ambos, a A referência em causa um erro em N3 tempo de compilação. Nessa situação, o conflito pode ser resolvido por meio da qualificação de referências a A, ou introduzindo um alias de importação que escolhe um determinado A, como no exemplo a seguir:

Imports N1
Imports N2
Imports A = N1.A

Namespace N3
    Class B
        Inherits A ' A means N1.A.
    End Class
End Namespace

Somente namespaces, classes, estruturas, tipos enumerados e módulos padrão podem ser importados.

Importações de namespace XML

Uma importação de namespace XML define um namespace ou o namespace padrão para expressões XML não qualificadas contidas na unidade de compilação.

XMLNamespaceImportsClause
    : '<' XMLNamespaceAttributeName XMLWhitespace? Equals XMLWhitespace?
      XMLNamespaceValue '>'
    ;

XMLNamespaceValue
    : DoubleQuoteCharacter XMLAttributeDoubleQuoteValueCharacter* DoubleQuoteCharacter
    | SingleQuoteCharacter XMLAttributeSingleQuoteValueCharacter* SingleQuoteCharacter
    ;

Por exemplo:

Imports <xmlns:db="http://example.org/database">

Module Test
    Sub Main()
        ' db namespace is "http://example.org/database"
        Dim x = <db:customer><db:Name>Bob</></>

        Console.WriteLine(x.<db:Name>)
    End Sub
End Module

Um namespace XML, incluindo o namespace padrão, só pode ser definido uma vez para um determinado conjunto de importações. Por exemplo:

Imports <xmlns:db="http://example.org/database-one">
' Error: namespace db is already defined
Imports <xmlns:db="http://example.org/database-two">

Namespaces

Os programas do Visual Basic são organizados usando namespaces. Os namespaces organizam internamente um programa e organizam a maneira como os elementos do programa são expostos a outros programas.

Ao contrário de outras entidades, os namespaces são abertos e podem ser declarados várias vezes dentro do mesmo programa e em vários programas, com cada declaração contribuindo com membros para o mesmo namespace. No exemplo a seguir, as duas declarações de namespace contribuem para o mesmo espaço de declaração, declarando duas classes com os nomes N1.N2.A totalmente qualificados e N1.N2.B.

Namespace N1.N2
    Class A
    End Class
End Namespace

Namespace N1.N2
    Class B
    End Class
End Namespace

Como as duas declarações contribuem para o mesmo espaço de declaração, seria um erro se cada uma contivesse uma declaração de um membro com o mesmo nome.

Há um namespace global que não tem nome e cujos namespaces e tipos aninhados sempre podem ser acessados sem qualificação. O escopo de um membro de namespace declarado no namespace global é todo o texto do programa. Caso contrário, o escopo de um tipo ou namespace declarado em um namespace cujo nome totalmente qualificado é N o texto do programa de cada namespace cujo nome totalmente qualificado do namespace correspondente começa com N ou é N ele mesmo. (Observe que um compilador pode optar por colocar declarações em um namespace específico por padrão. Isso não altera o fato de que ainda há um namespace global sem nome.)

Neste exemplo, a classe B pode ver a classe A porque Bo namespace N1.N2.N3 de 's está conceitualmente aninhado dentro do namespace N1.N2.

Namespace N1.N2
    Class A
    End Class
End Namespace

Namespace N1.N2.N3
    Class B
        Inherits A
    End Class
End Namespace

Declarações de namespace

Há três formas de declaração de namespace.

NamespaceDeclaration
    : 'Namespace' NamespaceName StatementTerminator
      NamespaceMemberDeclaration*
      'End' 'Namespace' StatementTerminator
    ;

NamespaceName
    : RelativeNamespaceName
    | 'Global'
    | 'Global' '.' RelativeNamespaceName
    ;

RelativeNamespaceName
    : Identifier ( Period IdentifierOrKeyword )*
    ;

O primeiro formulário começa com a palavra-chave Namespace seguida por um nome de namespace relativo. Se o nome de namespace relativo for qualificado, a declaração de namespace será tratada como se estivesse lexicamente aninhada dentro de declarações de namespace correspondentes a cada nome no nome qualificado. Por exemplo, os dois namespaces a seguir são semanticamente equivalentes:

Namespace N1.N2
    Class A
    End Class

    Class B
    End Class
End Namespace 

Namespace N1
    Namespace N2
        Class A
        End Class

        Class B
        End Class
    End Namespace
End Namespace

O segundo formulário começa com as palavras-chave Namespace Global. Ele é tratado como se todas as suas declarações de membro fossem lexicamente colocadas no namespace global sem nome -- independentemente de quaisquer padrões fornecidos pelo ambiente de compilação. Essa forma de declaração de namespace não pode ser aninhada lexicamente em nenhuma outra declaração de namespace.

O terceiro formulário começa com as palavras-chave Namespace Global seguidas de um identificador qualificado N. Ele é tratado como se fosse uma declaração de namespace do primeiro formulário "Namespace N" que foi lexicamente colocada no namespace global sem nome -- independentemente de quaisquer padrões fornecidos pelo ambiente de compilação. Essa forma de declaração de namespace não pode ser aninhada lexicamente em nenhuma outra declaração de namespace.

Namespace Global       ' Puts N1.A in the global namespace
    Namespace N1
        Class A
        End Class
    End Namespace
End Namespace

Namespace Global.N1    ' Equivalent to the above
    Class A
    End Class
End Namespace

Namespace N1           ' May or may not be equivalent to the above,
    Class A            ' depending on defaults provided by the
    End Class          ' compilation environment
End Namespace

Ao lidar com os membros de um namespace, não é importante onde um membro específico é declarado. Se dois programas definirem uma entidade com o mesmo nome no mesmo namespace, tentar resolver o nome no namespace causará um erro de ambiguidade.

Os namespaces são, por definição Public, portanto, uma declaração de namespace não pode incluir modificadores de acesso.

Membros Namespace

Os membros do namespace só podem ser declarações de namespace e declarações de tipo. Declarações de tipo podem ter Public ou Friend acesso. O acesso padrão para tipos é Friend acesso.

NamespaceMemberDeclaration
    : NamespaceDeclaration
    | TypeDeclaration
    ;

TypeDeclaration
    : ModuleDeclaration
    | NonModuleDeclaration
    ;

NonModuleDeclaration
    : EnumDeclaration
    | StructureDeclaration
    | InterfaceDeclaration
    | ClassDeclaration
    | DelegateDeclaration
    ;