Uma lista exaustiva de diferenças entre VB.NET e C #. Parte 1

imagem

De acordo com a classificação TIOBE em 2018, o VB.NET superou o C # em popularidade. Coincidência ou não, mas em fevereiro Eric Lippert, um dos criadores do C #, pediu aos leitores que prestassem atenção no blog de seu amigo, um ex-companheiro de equipe do compilador Roslyn e, em combinação, um ardente fã do VB.NET, Anthony Green . "Esses recursos são detalhes detalhados de especialistas que não são tão fáceis de encontrar lendo a documentação", escreve Eric. Apresentamos a sua atenção a primeira parte da tradução do artigo de Anthony Green, “Uma lista exaustiva de diferenças entre VB.NET e C #”. Talvez seja justamente nessas diferenças que reside o segredo da dinâmica da classificação dessas línguas.

Por quase metade da minha vida, testemunhei e participei de inúmeras discussões sobre quão semelhantes ou diferentes são as duas linguagens .NET mais populares. Primeiro, como amador, depois como profissional e, finalmente, como advogado de clientes, gerente de programas e designer de idiomas, posso dizer sem exagero quantas vezes ouvi ou li algo como:
"... VB.NET é realmente apenas uma camada fina em cima de IL, como C # ..."
ou
"... VB.NET é na verdade apenas C # sem ponto e vírgula ..."
Como se os idiomas fossem uma conversão XML ou uma folha de estilos.

E se algum visitante ardente não escrever isso em um comentário, muitas vezes isso está implícito em perguntas como: “Olá, Anthony! Me deparei com uma diferença tão pequena em um único lugar - isso é um bug? Como poderiam essas duas línguas idênticas, que deveriam ser idênticas em nome de todos os bons e santos neste mundo, se dispersar nesse único lugar? Por que precisamos de tanta injustiça ?!

" Separados " , como se fossem os mesmos até que uma mutação ocorresse e depois se tornassem espécies separadas. Hah!

Mas eu entendo isso. Antes de ingressar na Microsoft, eu também poderia ter se apegado a essa ideia e usado como argumento para responder aos oponentes ou tranquilizar alguém. Eu entendo o charme dela. É fácil de entender e muito fácil de repetir. Mas trabalhando em Roslyn (essencialmente reescrevendo VB e C # completamente do zero) por 5 anos, percebi o quão inequivocamente essa idéia é falsa . Trabalhei com uma equipe de desenvolvedores e testadores para reimplementar cada centímetro de ambos os idiomas, bem como suas ferramentas em uma enorme solução de múltiplos projetos com milhões de linhas de código escritas nos dois idiomas. E levando em conta o grande número de desenvolvedores que alternam entre eles, o alto nível de compatibilidade com os resultados e a experiência das versões anteriores, bem como a necessidade de reproduzir de forma confiável o volume gigantesco da API em grande detalhe, fui forçado a conhecer as diferenças de perto. De fato, às vezes me pareceu que aprendi algo novo sobre o VB.NET (meu idioma favorito) todos os dias.

E, finalmente, dediquei um tempo para sentar e descarregar do cérebro uma partícula do que aprendi usando e criando o VB.NET nos últimos 15 anos, na esperança de que eu possa pelo menos economizar meu tempo da próxima vez.

Antes de passar para a lista, descreverei as regras básicas:

  • Esta lista não é exaustiva no sentido usual. Ele é exaustivo. Essas não são todas as diferenças. Essas nem são todas as diferenças que eu geralmente conheço. Essas são apenas as diferenças que me lembro primeiro, até ficar cansado demais para continuar; até eu ficar sem força. Se eu ou algum de vocês conhecer ou recordar outras diferenças, terei o prazer de atualizar esta lista.
  • Iniciarei no início da especificação do VB 11 e desço usando seu conteúdo para me lembrar das diferenças que vêm à mente primeiro sobre este tópico.
  • Esta NÃO é uma lista de funções no VB que não estão em C #. Portanto, não há "literais XML versus ponteiros". Isso é muito comum e já existem inúmeras listas na Internet (algumas das quais foram escritas por mim, e talvez no futuro eu escreva mais). Vou me concentrar principalmente nas construções que têm um análogo nos dois idiomas, e onde um observador desinformado pode sugerir que essas duas coisas se comportam da mesma forma, mas onde existem diferenças pequenas ou grandes; eles podem parecer iguais, mas funcionam de maneira diferente ou, por fim, geram código diferente.
  • Esta NÃO é uma lista de diferenças sintáticas entre VB e C # (que são incontáveis). Vou falar principalmente sobre diferenças semânticas (o que as coisas significam), e não sobre as sintáticas (como as coisas são escritas). Portanto, nenhuma peça como "VB inicia comentários com 'e C # usa //" ou "em C # _ é um identificador válido, mas não em VB". Mas vou quebrar essa regra em vários casos. Afinal, a primeira seção da especificação é sobre regras lexicais.
  • Freqüentemente, darei exemplos e, às vezes, apresentarei justificativas para o design ser de uma maneira ou de outra. Algumas decisões de design foram tomadas diante de meus olhos, mas a grande maioria precedeu meu tempo, e só posso adivinhar por que elas foram tomadas.
  • Deixe um comentário ou envie um tweet para mim ( @ThatVBGuy ) para informar suas diferenças favoritas e / ou sobre as quais você gostaria de saber mais.

Tendo definido expectativas e sem mais atrasos ...

Conteúdo


Texto oculto

Sintaxe e pré-processamento



Anúncios, etc.



Instruções



Sintaxe e pré-processamento


1. Palavras-chave e operadores VB podem usar caracteres de largura total


Alguns idiomas (não sei exatamente quantos, mas pelo menos em algumas formas de chinês, japonês e coreano) usam caracteres de largura total. Em resumo, isso significa que, ao usar uma fonte monoespaçada (como a maioria dos programadores), o caractere chinês ocupa duas vezes mais espaço horizontal do que os caracteres latinos que costumávamos ver no oeste. Por exemplo:



Aqui eu tenho uma declaração de variável escrita em japonês e inicialização com uma string também escrita em japonês. Segundo o tradutor do Bing, a variável é chamada "saudação" e a linha diz "Olá, mundo!" O nome da variável em japonês tem apenas 2 caracteres, mas ocupa o espaço de 4 caracteres de meia largura que meu teclado normalmente fornece, como demonstra o primeiro comentário. Existem versões de largura total de números e todos os outros caracteres ASCII impressos que têm a mesma largura que os japoneses. Para demonstrar isso, escrevi um segundo comentário usando os números de largura total "1" e "2". Estes não são "1" e "2" como no primeiro comentário. Não há espaços entre os números. Você também pode ver que o tamanho dos caracteres não tem exatamente 2 caracteres de largura, existe um pequeno deslocamento. Isso ocorre em parte porque esse programa combina caracteres de largura e meia largura em uma linha e nas três linhas.

Os espaços têm meia largura, os caracteres alfanuméricos têm largura total. Não somos programadores, a menos que seja obcecado pelo alinhamento de texto. E parece-me que, se você é chinês, japonês ou coreano (ou alguém que usa caracteres de tamanho completo no idioma) e usa identificadores ou strings escritos no idioma nativo, esses pequenos erros de alinhamento são irritantes.

Pelo que entendi, dependendo do seu teclado japonês, é fácil alternar entre hieróglifos e latim, mas é preferível usar caracteres latinos de largura total. O VB suporta isso em palavras-chave, espaços, operadores e até aspas. Então, tudo isso pode ser escrito assim:



Como você pode ver, nesta versão, palavras-chave, espaços, comentários, operadores e até aspas usam suas versões em tamanho real. No caos trouxe ordem.

Sim Os japoneses usam VB. De fato, apesar da sintaxe semelhante ao idioma inglês (e talvez por isso), para a maioria dos usuários de VB que vejo nos fóruns, o inglês não é o idioma principal. Durante meu trabalho na Microsoft, conheci o MVP VB japonês várias vezes, pelo menos um deles sempre trazia doces japoneses. Se você é um programador de VB da China, Japão ou Coréia (ou de qualquer outro país que use caracteres de largura total), escreva nos comentários. (Nos comentários ao autor, eles escreveram que os japoneses estão tentando em todo lugar no código usar ascii - aprox. Por. )

Momento engraçado: quando eu implementei linhas interpoladas no VB, eu (para minha vergonha) não levei em conta a possibilidade de colchetes de largura total nos locais de substituição. Vladimir Reshetnikov ( @vreshetnikov ) descobriu e corrigiu esse erro, para que a grande tradição de tolerância de VB na largura de caracteres permanecesse válida.

2. VB suporta aspas inteligentes


Ok, isso é, obviamente, um pouco, mas digno de menção. Você já viu um exemplo de código em um documento de texto como este:



E depois de copiar o exemplo no seu código, você descobriu que nenhuma das aspas (destacadas) funcionava, porque o Word substitui todas as aspas ASCII comuns por aspas inteligentes ?

Eu não Ok, eu tinha, mas somente quando copiei os exemplos para o C #. No VB, aspas inteligentes são delimitadores válidos para seqüências de caracteres (é engraçado que as aspas «» russas não funcionem - Aprox. Por.):



Eles também trabalham dentro de strings, embora talvez de uma maneira estranha. Se você duplica as aspas inteligentes para escapar, tudo o que você obtém no tempo de execução é apenas as aspas simples ("burras"). Isso pode parecer um pouco estranho, mas, no entanto, muito prático, já que quase em qualquer outro lugar citações inteligentes são permitidas na string. O compilador NÃO faz com que você termine com uma cotação inteligente ou use a correta se você começou com uma cotação inteligente, para que você possa misturá-lo sem se preocupar. E sim, isso também funciona com o caractere de aspas simples usado para comentários:



Tentei convencer Paul Wick ( @panopticoncntrl ) a admitir que ele fez isso apenas porque estava atormentado com esse problema enquanto trabalhava na especificação, mas ele nega qualquer culpa. Este não foi o caso no VB6, então alguém adicionou isso mais tarde.

3. As constantes de pré-processamento podem ser de qualquer tipo primitivo (incluindo datas) e podem conter qualquer valor constante



4. Operadores aritméticos podem ser usados ​​em expressões de pré-processamento




Anúncios, etc.


5. O VB às vezes ignora as declarações de implementações de IL para impedir a implementação implícita acidental de uma interface por nome.


Este item é da categoria de esoterismo. No VB, a implementação de uma interface é sempre feita explicitamente. Mas, na ausência de uma implementação explícita, o comportamento padrão do CLR ao chamar um método de interface é procurar métodos públicos por nome e assinatura. Na maioria dos casos, isso é normal, porque no VB você geralmente precisa fornecer uma implementação para cada membro da interface implementada, exceto um caso:

 Interface IFoo Sub Bar() Sub Baz() End Interface Class Foo Implements IFoo Private Sub Bar() Implements IFoo.Bar Exit Sub End Sub Private Sub IFoo_Baz() Implements IFoo.Baz Exit Sub End Sub End Class Class FooDerived Inherits Foo Implements IFoo Public Sub Bar() Implements IFoo.Bar Exit Sub End Sub Public Sub Baz() ' Does something unrelated to what an IFoo.Baz would do. End Sub End Class 

gist.github.com/AnthonyDGreen/39634fd98a0cacc093719ab62d7ab1e6#file-partial-re-implementation-vb

Neste exemplo, a classe FooDerived apenas deseja reatribuir IFoo.Bar para o novo método, mas mantém as implementações restantes inalteradas. Acontece que se o compilador simplesmente gerar a diretiva implementa para o FooDerived , o CLR também selecionará o FooDerived.Baz como uma nova implementação do IFoo.Baz (embora neste exemplo não esteja relacionado ao IFoo ). No C #, isso acontece implicitamente (e não tenho certeza se posso recusá-lo), mas no VB o compilador realmente omite 'Implementações' de toda a declaração para evitar isso e redefine apenas membros específicos que foram reimplementados. Em outras palavras, se você perguntar ao FooDerived se ele IFoo diretamente, ele dirá não:


Por que eu sei disso e por que é importante? Por muitos anos, os usuários do VB solicitaram suporte para implementação implícita de uma interface (sem especificar explicitamente Implements em cada declaração), geralmente para geração de código. Apenas incorporar isso à sintaxe atual seria uma mudança FooDerived.Baz , porque o FooDerived.Baz agora implementa implicitamente o IFoo.Baz , embora ainda não tenha feito isso antes. Mais recentemente, porém, aprendi mais sobre esse comportamento ao discutir possíveis problemas de design com o recurso "implementação de interface padrão", que permitiria que as interfaces incluíssem implementações padrão de alguns membros e não exigiriam reimplementação em cada classe. Isso seria útil para sobrecargas, por exemplo, quando é provável que a implementação seja a mesma para todos os implementadores (delegação à sobrecarga principal). Outro cenário é o controle de versão. Se uma interface puder incluir implementações padrão, você poderá adicionar novos membros a ela sem interromper as implementações antigas. Mas há um problema. Como o comportamento padrão no CLR é procurar implementações públicas por nome e assinatura, se a classe VB não implementar membros da interface com implementações padrão, mas tiver membros públicos com nome e assinatura adequados, eles implicitamente implementarão esses membros da interface, mesmo se você fizer isso completamente. não deveria. Há coisas que você pode fazer para contornar isso quando o conjunto completo de membros da interface é conhecido no momento da compilação. Porém, caso o membro tenha sido adicionado após a compilação do código, ele muda silenciosamente o comportamento no tempo de execução.

6. O VB, por padrão, oculta os membros da classe base por nome (Shadows), e não por nome e assinatura (Sobrecargas)


Eu acho que essa diferença é bem conhecida. O cenário é o seguinte: você herda a classe base ( DomainObject ), possivelmente fora de seu controle, e declara um método com um nome que faz sentido no contexto da sua classe, por exemplo, Print :

 Class DomainObject End Class Class Invoice Inherits DomainObject Public Sub Print(copies As Integer) ' Sends contents of invoice to default printer. End Sub End Class 

gist.github.com/AnthonyDGreen/863cfd1e7536fe8bda7cd145795eaf9f#file-shadows-example-vb

O fato de uma fatura poder ser impressa faz sentido. Mas na próxima versão da API em que sua classe base é declarada, eles decidem pela depuração para adicionar a todos os DomainObjects um método que exibe o conteúdo completo do objeto na janela de depuração. Este método é chamado brilhantemente de impressão. O problema é que o cliente da sua API pode perceber que o objeto Invoice possui os métodos Print() e Print(Integer) e acha que essas sobrecargas estão relacionadas. Talvez o primeiro apenas imprima uma cópia. Mas isso não é exatamente o que você concebeu como autor da fatura. Você não tinha idéia de que DomainObject.Print aparecesse. Então, sim, isso não funciona no VB. Quando essa situação aparece, um aviso é exibido, mas o mais importante é que o comportamento padrão no VB é ocultar pelo nome. Ou seja, até que você indique explicitamente com a palavra-chave Overloads que Print é uma sobrecarga de Print classe base, um membro da classe base (e qualquer sobrecarga dela) fica completamente oculto. Somente a API que você declarou originalmente é mostrada aos seus clientes de classe. Isso funciona por padrão, mas você pode fazê-lo explicitamente através da palavra-chave Shadows. O C # só pode fazer Overloads (embora leve em consideração as Shadows quando se refere a uma biblioteca VB) e o faz por padrão (usando a new palavra-chave). Mas essa diferença aparece de tempos em tempos quando algumas hierarquias de herança aparecem em projetos nos quais uma classe é definida em um idioma e outra em outro, e há métodos sobrecarregados, mas isso está além do escopo do item atual na lista de diferenças.

7. VB11 e abaixo são mais rigorosos para membros protegidos em genéricos


De fato, alteramos isso entre o VS2013 e o VS2015. Em particular, decidimos não nos preocupar com a reimplementação. Mas estou escrevendo essa distinção, caso você use a versão antiga e observe. Resumindo: se um membro Protegido for declarado no tipo genérico, o herdeiro, sendo também um genérico, poderá acessar esse membro protegido apenas através da instância herdada com os mesmos argumentos de tipo.

 Class Base(Of T) Protected x As T End Class Class Derived(Of T) Inherits Base(Of T) Public Sub F(y As Derived(Of String)) ' Error: Derived(Of T) cannot access Derived(Of String)'s ' protected members yx = "a" End Sub End Class 

gist.github.com/AnthonyDGreen/ce12ac986219eb51d6c85fa02c339a2f#file-protected-in-generics-vb

8. A sintaxe “argumento nomeado” nos atributos sempre inicializa propriedades / campos


O VB usa a mesma sintaxe := para inicializar propriedades / campos de atributos e para passar argumentos de método por nome. Portanto, não há como passar um argumento para o construtor de atributos por nome.

9. Todas as declarações de nível superior (geralmente) estão implicitamente localizadas no espaço para nome raiz do projeto


Essa diferença está quase na categoria "recursos avançados", mas eu a incluí na lista porque altera o significado do código. Há um campo nas propriedades do projeto VB:


Por padrão, este é simplesmente o nome do seu projeto no momento da criação. Esse não é o mesmo campo que o "Namespace padrão" nas propriedades de um projeto C #. O espaço para nome padrão simplesmente define qual código é adicionado por padrão aos novos arquivos em C #. Mas o espaço para nome raiz no VB significa que, a menos que seja indicado de outra forma, todas as declarações de nível superior neste projeto estão implicitamente nesse espaço para nome. É por isso que os modelos de documento do VB geralmente não contêm nenhuma declaração de namespace. Além disso, se você adicionar uma declaração de namespace, ela não substitui a raiz, mas é adicionada a ela:

 Namespace Controllers ' Child namespace. End Namespace Namespace Global.Controllers ' Top-level namespace End Namespace 

gist.github.com/AnthonyDGreen/fd1e5e3a58aee862a5082e1d2b078084#file-root-namespace-vb

Portanto, o espaço para nome Controllers realmente declara o espaço VBExamples.Controllers nome VBExamples.Controllers , a menos que você se livre desse mecanismo declarando VBExamples.Controllers explicitamente no espaço para nome.

Isso é conveniente porque salva um nível de recuo e um conceito extra para cada arquivo VB. E isso é especialmente útil se você estiver criando um aplicativo UWP (porque tudo deve estar no espaço para nome no UWP) e é extremamente conveniente se você decidir alterar o espaço para nome de nível superior para todo o seu projeto, por exemplo, de um nome de código como Roslyn para uma versão mais longa, como o Microsoft.CodeAnalysis , já que você não precisa atualizar manualmente cada arquivo na solução. Também é importante ter isso em mente ao trabalhar com geradores de código, espaços para nome XAML e o novo .vbproj arquivo .vbproj .

10. Os módulos não são gerados como classes abstratas seladas em IL, portanto, não se parecem exatamente com classes estáticas de C # e vice-versa.


Os módulos no VB existiam antes das classes estáticas de C #, embora tentássemos em 2010 torná-los iguais em termos de IL. Infelizmente, essa foi uma mudança radical, porque o serializador de XML (ou talvez fosse binário) para essa versão do .NET (acho que eles o corrigiram) não queria serializar o tipo aninhado em um tipo que não pôde ser criado (e uma classe abstrata não pode). Ele lançou uma exceção.

Descobrimos isso depois de fazer as alterações e revertê-las porque algum código em algum lugar usava o tipo de enum incorporado no módulo. E como você não sabe com qual versão do serializador o programa compilado funcionará, nunca será possível alterá-lo, pois ele funcionará em uma versão do aplicativo e lançará exceções em outras.

11. Você não precisa de um método explícito para o ponto de entrada (Sub Main) nos aplicativos WinForms


Se o seu projeto usa Form como o objeto inicial e não usa o "Application Framework" (mais sobre isso na próxima postagem), o VB gera um Sub Main , que cria seu formulário inicial e o passa para Application.Run , economizando o arquivo inteiro Para gerenciar esse processo, seja um método adicional no seu Form ou até a necessidade de pensar sobre esse problema.

12. Se você chamar alguns métodos de tempo de execução do VB herdados (por exemplo, FileOpen), o método de chamada será implicitamente marcado com um atributo para desativar o inlining por motivos de correção.


Em resumo, os métodos para trabalhar com arquivos no estilo VB6, como o FileOpen contam com contextos específicos para o assembly em que o código está localizado. Por exemplo, o arquivo 1 pode ser um log em um projeto e uma configuração em outro. Para determinar qual assembly está sendo executado, Assembly.GetCallingAssembly() chamado. Mas se o JIT incluir o método no chamador, do ponto de vista da pilha, o método de tempo de execução do VB não será chamado pelo método, mas pelo chamador, que pode estar em um assembly diferente, o que permitirá que seu código acesse ou viole o estado interno do chamador objeto Este não é um problema de segurança, porque se o código comprometedor estiver em execução no seu processo, você já estará perdido. Isso é uma questão de correção. Portanto, se você usar esses métodos, o compilador desativará o inlining.

Essa alteração foi feita no último momento em 2010, porque o x64 JIT é MUITO agressivo ao inserir / otimizar o código, e achamos muito tarde, e era a opção mais segura.

13. Se o seu tipo estiver marcado com o atributo DesignerGenerated e não contiver nenhuma declaração explícita do construtor, o compilador gerado por padrão chamará InitializeComponent se estiver definido para esse tipo.


Na era anterior ao advento dos tipos Parciais, a equipe do VB travou uma guerra para reduzir o código padrão nos projetos WinForms. Mas mesmo com Partial isso é útil porque permite que o arquivo gerado omita completamente o construtor, e o usuário pode declara-lo manualmente em seu arquivo, se necessário, ou não. Sem isso, o designer seria forçado a adicionar um construtor apenas para chamar InitializeComponent , e se o usuário adicionar também, eles serão duplicados ou o kit de ferramentas deve ser inteligente o suficiente para mover o construtor do arquivo de designer para o usuário e não o regenerar no designer, se ele já existe no arquivo do usuário.

14. A ausência do modificador Parcial NÃO significa que o tipo não é parcial


Tecnicamente, no VB, apenas uma classe deve ser marcada como Parcial. Isso geralmente é (em projetos de GUI) um arquivo gerado.

Porque Isso mantém o arquivo do usuário bonito e limpo, e pode ser muito conveniente incluir após gerar ou adicionar código personalizado ao código gerado. No entanto, é recomendável que no máximo uma classe não tenha modificador Parcial, caso contrário, um aviso será emitido.

15. Nas classes padrão, o nível de acesso público é para tudo, exceto campos, e em estruturas públicas, também para campos


Eu tenho sentimentos mistos sobre isso. No C #, tudo é private por padrão (saúde, encapsulamento!), Mas há um argumento a ser feito, dependendo do que você costuma declarar: um contrato público ou detalhes de implementação. Propriedades e eventos são geralmente destinados ao uso externo ( public ) e os operadores não podem ser acessados ​​além de public . No entanto, raramente confio na acessibilidade padrão (com exceção de demos como os exemplos deste artigo).

16. VB inicializa os campos APÓS o construtor de base ser chamado, enquanto o C # os inicializa ANTES de o construtor de base ser chamado


Você já ouviu falar como "alguns" dizem que a primeira coisa que acontece no construtor é uma chamada para o construtor da classe base? Bem, esse não é o caso, pelo menos em C #. Em C #, antes de chamar base() , explícita ou implícita, os inicializadores de campo são executados primeiro, depois a chamada do construtor e, em seguida, o seu código. Essa decisão tem consequências, e acho que sei por que os desenvolvedores de idiomas podem seguir um caminho ou outro. Acredito que uma dessas consequências é que o código a seguir não pode ser traduzido diretamente para c #:

 Imports System.Reflection Class ReflectionFoo Private StringType As Type = GetType(String) Private StringLengthProperty As PropertyInfo = StringType.GetProperty("Length") Private StringGetEnumeratorMethod As MethodInfo = StringType.GetMethod("GetEnumerator") Private StringEnumeratorType As Type = StringGetEnumeratorMethod.ReturnType Sub New() Console.WriteLine(StringType) End Sub End Class 

gist.github.com/AnthonyDGreen/37d01c8e7f085e06172bfaf6a1e567d4#file-field-init-me-reference-vb

Nos dias em que eu estava envolvido com o Reflection, muitas vezes escrevia esse código. E eu me lembro vagamente de um colega da Microsoft (Josh), que traduziu meu código em C #, às vezes reclamando da necessidade de portar todos os meus inicializadores para o construtor. Em C #, é proibido se referir a um objeto que está sendo criado antes que base() seja chamada. E como os inicializadores de campo são executados antes da chamada especificada, eles também não podem se referir a outros campos ou a nenhum membro da instância do objeto. Portanto, este exemplo também funciona apenas no VB:

 MustInherit Class Base ' OOP OP? Private Cached As Object = DerivedFactory() Protected MustOverride Function DerivedFactory() As Object End Class Class Derived Inherits Base Protected Overrides Function DerivedFactory() As Object Return New Object() End Function End Class 

gist.github.com/AnthonyDGreen/fe5ca89e5a98efee97ffee93aa684e50#file-base-derived-init-vb

Aqui temos uma classe base, que supostamente possui muita funcionalidade, mas precisa de algum objeto-chave para gerenciamento, trabalho, que é determinado pelo tipo derivado. Existem várias maneiras de implementar esse modelo, mas eu geralmente uso esse porque:

  • ele é baixo;
  • não exige que eu declare um construtor;
  • não exige que eu coloque o código de inicialização no construtor, se houver;
  • permite que eu armazene em cache o objeto criado e não exige que tipos derivados declarem e gerenciem o armazenamento para o objeto fornecido, embora agora isso não seja um problema com as propriedades automáticas.

Além disso, eu já estive nas duas situações: quando um campo em um tipo derivado queria chamar um método declarado na classe base e quando o inicializador da classe de campo precisava chamar o membro MustOverride implementado pelo tipo derivado. Ambos são válidos em VB e nenhum em C #, e isso faz sentido. Se o inicializador de campo C # pudesse chamar um membro da classe base, esse membro poderia depender dos campos inicializados no construtor base (que ainda não está sendo executado) e os resultados quase certamente estariam errados, e não havia como contornar isso.

Mas no VB, o construtor de base já funcionou, então você pode fazer qualquer coisa! Na situação oposta, tudo é um pouco mais complicado, porque chamar o membro Overridable do inicializador (ou construtor) da classe base pode levar ao acesso aos campos antes que eles sejam "inicializados". Mas apenas sua implementação sabe se isso é um problema. Nos meus scripts, isso simplesmente não acontece. Eles não dependem do estado da instância, mas não podem ser membros Shared porque você não pode ter um membro Shared Overridable em nenhum idioma por motivos técnicos além do escopo deste artigo. Além disso, está claramente definido o que acontece com os campos antes de iniciar inicializadores personalizados - eles são inicializados com valores padrão, como todas as variáveis ​​no VB. Sem surpresas.

Então porque? Na verdade, não sei se meus scripts eram o que a equipe original do VB.NET tinha em mente quando o projetaram. Apenas funciona no meu caso! , : VB , , . . .

, , , C# VB, , VB , C#.

17. (backing field) VB , C#,


( ). E , VB ( IDE) EEvent . C# E , , E , .

18. VB


P , _P' . IntelliSense, . C# «» ( mangled ) , , C# .

? VB , -, «WithEvents», -, , - , .

19. read-only


, , …. VB « » . WithEvents -, non-Custom , , . IntelliSense, , , . FTW! , VB , private set; C#.

 Class Alarm Private ReadOnly Code As Integer ReadOnly Property Status As String = "Disarmed" Sub New(code As Integer) Me.Code = code End Sub Sub Arm() ' I'm motifying the value of this externally read-only property here. _Status = "Armed" End Sub Function Disarm(code As Integer) As Boolean If code = Me.Code Then ' And here. _Status = "Disarmed" Return True Else Return False End If End Function End Class 

gist.github.com/AnthonyDGreen/57ce7962700c5498894ad417296f9066#file-read-only-auto-property-backing-field-is-writeable-vb

20.


, NonSerialized .

VB (expanded) Custom- 2005 (?) , , , NonSerialized . , , , , «» , « ».

, , , , . , , , , two-way bindable ( , PropertyChanged ), , , , , .

, , CLSA «Expert Business Objects» (Rocky Lhotka) , undo/redo ( , , - , , ), . , . , , , .


21. — , ; ( )


, GoTo . , - . , For For Each ; Using , SyncLock With , , , Finally . If Select Case , Do While , Try — , :

 Module Program Sub Main() Dim retryCount = 0 Try Retry: ' IO call. Catch ex As IO.IOException When retryCount < 3 retryCount += 1 GoTo Retry End Try End Sub End Module 

gist.github.com/AnthonyDGreen/b93adcf3c3705e4768dcab0b05b187a0#file-try-goto-retry-vb

, , , .NET VB «». VB6 Quick Basic ( ) . QB, . , « », . GoTo, — , .

: Try , VB - await Catch Finally , , GoTo .

22. <>


, VB ( ) ( static ) ( ). , . Catch 3 . Try Catch , , , Try .

, VB.NET , . CLR VB . : , .

, C# , , «». VB.NET .

23.


, , C# « » ( definite assignment ). , , , « ». , ( ) , , . C/C++. , ! , . , , , — . , , , , , , , , . , BASIC , , «» , = Nothing , = 0 , = False ..

, ( flow analysis ) VB , .

, C# , , , . VB , , , . Roslyn, , API « », , .

24. RaiseEvent , null


, - C# VB. RaiseEvent VB — , null ( ), null - — , .

 ' You don't have to write this: If PropertyChangedEvent IsNot Nothing Then RaiseEvent PropertyChanged(Me, e) End If ' You don't have to write this: Dim handlers = PropertyChangedEvent If handlers IsNot Nothing Then handlers(Me, e) End If ' You don't have to write this either: PropertyChangedEvent?(Me, e) ' Just write this: RaiseEvent PropertyChanged(Me, e) 

gist.github.com/AnthonyDGreen/c3dea3d91ef4ffc50cfa92c41f967937#file-null-safe-event-raising-vb

, null-conditional C# VS2015 C# , VB ( ), , ; VB.NET .

25. ; (shallow clone)


, , 17 , , . (boxed) Object, System.Runtime.CompilerServices.RuntimeHelper.GetObjectValue . , CLR. , :

  • , .
  • , (, Integer ), .
  • , .

, , , , ( late-bound situations ). , , ( ) , , , , ( caller's copy ). , , - , — .

. :

 Class MyEventArgs Property Value As Object End Class Structure MyStruct Public X, Y As Integer End Structure Module Program Sub Main() Dim defaultValue As Object = New MyStruct With {.X = 3, .Y = 5} Dim e = New MyEventArgs With {.Value = defaultValue} RaiseEvent DoSomething(Nothing, e) If e.Value Is defaultValue Then ' No handlers have changed anything. Console.WriteLine("Unchanged.") End If End Sub Event DoSomething(sender As Object, e As MyEventArgs) End Module 

gist.github.com/AnthonyDGreen/422ac4574af92d9bbbf59f0fbc40b74d#file-get-object-value-vb

, WPF, . , . , , . , . , - , , , .

, , « » . IronRuby/Python, dynamic C# ( C#): C# GetObjectValue . object.ReferenceEquals , , , - dynamic C# ( ). == , . C#, , .

26. Select Case «» (fall-through); break


Friday , Sunday — , 5 .

 Module Program Sub Main() Select Case Today.DayOfWeek Case DayOfWeek.Monday: Case DayOfWeek.Tuesday: Case DayOfWeek.Wednesday: Case DayOfWeek.Thursday: Case DayOfWeek.Friday: Console.WriteLine("Weekday") Case DayOfWeek.Saturday: Case DayOfWeek.Sunday: Console.WriteLine("Weekend") End Select End Sub End Module 

gist.github.com/AnthonyDGreen/7b7e136c71dd11b2417a6c7267bb3546#file-select-case-no-fallthrough-vb

Roslyn C# , - : «, ? !» «, » . . VS , , , , , . !

. C# , C, C. . , C# , case . - , goto , break . VB break , Exit Select , , VB .

27. Case


, . C#, :

 Module Program Sub Main() Select Case Today.DayOfWeek Case DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday Dim message = "Get to work!" Case DayOfWeek.Saturday, DayOfWeek.Sunday Dim message = "Funtime!" End Select End Sub End Module 

gist.github.com/AnthonyDGreen/bd642061896246c9336255881fb78546#file-select-case-scopes-vb

, message , C# switch case — . . , , - ( , C): , , , , .

28, 29, 30. Select Case , =


, , , , Select Case .

, , . :

  • Select Case — , , …
  • switch — / , « ».

, 26-30. switch , , , , if . IL switch , , If , VB , . switch , , C . VB .

31. , ,


x , , -1, -2, -3:

 Module Program Sub Main() For i = 1 To 3 Dim x As Integer x -= 1 Console.WriteLine(x) Next End Sub End Module 

gist.github.com/AnthonyDGreen/cbc3a9c70677354973d64f1d993a3c5d#file-loop-variables-retain-their-values-vb

« , , » ( ). , VB2008 , -:

 Module Program Sub Main() Dim lambdas = New List(Of Action) For i = 1 To 3 Dim x As Integer x -= 1 lambdas.Add(Sub() Console.WriteLine(x)) Next For Each lambda In lambdas lambda() Next End Sub End Module 

gist.github.com/AnthonyDGreen/2ef9ba3dfcf9a1abe0e94b0cde12faf1#file-loop-variables-captured-per-iteration-vb

-1, -2, -3. x — « », - x , . , x . flow analysis API — ! ( «… … ?» )

? , , , , , #22. , , -, .

, VB C# ( control variables ) For Each VS2012 (?), - « ». 10000% , ( , VB , ). , VB For , . , . , VB For For Each , for foreach C#. , For VB - , , .

32. For


For . , , 1,3,5,7,9, , .

 Module Program Sub Main() Dim lower = 1, upper = 9, increment = 2 For i = lower To upper Step increment Console.WriteLine(i) upper += 1 increment -= 1 Next End Sub End Module 

gist.github.com/AnthonyDGreen/1e48113be204f515c51e221858666ac7#file-for-loop-bounds-cached-vb

, ( ), , , , IndexOutOfRangeExceptions , .

, , , , , C, VB . - , VB , For i = a To b Step c ( , i> b ) ( , i <b ), c ? , , , b , — . , , , , .

33. For Each VB GetEnumerator


For Each , IEnumerable , GetEnumerator , For Each .
, , For Each IEnumerator , , :

 Module Program Sub Main() Dim list = New List(Of Integer) From {1, 2, 3, 4, 5} Dim info = list.FirstAndRest() If info.First IsNot Nothing Then Console.Write(info.First.GetValueOrDefault()) For Each other In info.Additional Console.Write(", ") Console.Write(other) Next Console.WriteLine() End If End Sub <Runtime.CompilerServices.Extension> Function FirstAndRest(Of T As Structure)(sequence As IEnumerable(Of T)) As (First As T?, Additional As IEnumerator(Of T)) Dim enumerator = sequence.GetEnumerator() If enumerator.MoveNext() Then Return (enumerator.Current, enumerator) Else Return (Nothing, enumerator) End If End Function <Runtime.CompilerServices.Extension> Function GetEnumerator(Of T)(enumerator As IEnumerator(Of T)) As IEnumerator(Of T) Return enumerator End Function End Module 

gist.github.com/AnthonyDGreen/d7dbb7a5b98a940765c4adc33e3eaeee#file-for-each-extension-get-enumerator-vb

F# , IEnumerator , For Each , .

VB , ( well-known name ), . , , Add, . C# , (. async / await ). , C# Roslyn () , .
Minuto de publicidade. 15-16 - .NET- DotNext 2019 Piter . , . , . .

Source: https://habr.com/ru/post/pt443684/


All Articles