Showing posts with label português. Show all posts
Showing posts with label português. Show all posts

Mar 1, 2025

Structs em C# - diversão garantida - Parte 8/9: Struct usada como valores padrão para argumentos

Read this post in English.

Lire cet article en français.

Em um post anterior examinamos o uso de parâmetros com valores default em construtores de structs; desta vez, vamos dar uma olhada rápida no que acontece quando temos parâmetros do tipo struct com valores default. Então, sem mais delongas, vamos começar com a seguinte struct1:

struct S
{
    public int v = 42;
    public S(string s) { v = 13; }
}

e o seguinte uso:

M();

void M(S s = new S()) => Console.WriteLine(s.v);

Qual saída você espera obter?

Se você tem acompanhado esta série de posts provavelmente notou que esta é uma pergunta capciosa e que nem 42 nem 13 são respostas corretas uma vez que para que tais valores pudessem ser impressos isso exigiria que o inicializador de campo e/ou o construtor fossem executados, mas a expressão new S() usada como valor default em parâmetros não implica uma invocação de construtor (afinal, não há um construtor sem parâmetros e, caso você esteja tentado a adicionar um, o compilador emitirá um erro, pois, na presença de tal construtor, a expressão em questão não representa mais uma constante de tempo de compilação, portanto, não pode ser usada como um valor default).

Na realidade, quando executado o trecho de código exibirá 0 pois o compilador simplesmente inicializa a memória usada para armazenar a instância da struct com zeros (0). Por outro lado, se você invocar o método M() como a seguir2:

M(new S("Foo"));

o compilador emitirá código para executar o construtor e o inicializador de campo e 13 será impresso, mas isso não tem mais nada a ver com o valor de parâmetro padrão.

E com isso exploramos o último comportamento não tão intuitivo de estruturas que pretendíamos abordar; o próximo post será a conclusão desta série.

Como sempre, todos os comentários são bem-vindos.

Divirta-se!


  1. Observe que o suporte para inicializadores de campo em structs foi introduzido no C# 10.

  2. O conteúdo da string não é importante neste contexto. Tudo o que importa é que um construtor seja invocado.

Jul 31, 2023

Structs em C# - diversão garantida - Parte 2/9: Uma breve introdução a Value types versus Reference types

Read this post in English.

Lisez cet post en french.

  1. Structs em C# - diversão garantida
  2. Rápida introdução à Value Types vs Reference Types (este post).
  3.  Initialização de campos em estruturas.
  4.  Comportamento de construtores em estruturas.
  5.  Outros cenários em que o comportamento de construtores em estruturas podem te surpreender.
  6.  Argumentos default  em construtores de estruturas (você ainda não esta confuso ?).
  7.  Modificador `required` do C# 11 não vai salvar seu c* trabalho.
  8.  Estruturas usadas como valor default de argumentos.
  9.  Bonus: Evolução das estruturas em C#. 


Desde o lançamento oficial da primeira versão do C# (versão 1.0, em janeiro/2002),  desenvolvedores se deparam com uma decisão ao introduzir novos tipos: declará-los como class (Reference Type) ou struct (Value Type), representados no .NET por System.Object e System.ValueType respectivamente.

Escolher um ou o outro tem implicações não triviais com relação à usabilidade, desempenho e extensibilidade, para listar alguns. Neste post, quero cobrir brevemente as principais diferenças (mais detalhes serão apresentados nos posts futuros se necessário) e esclarecer uma concepção equivocada (a qual provavelmente sou culpado por contribuir para sua disseminassão). Então, sem mais delongas, vamos entrar na distinção mais importante sobre os dois...

Semântia de referencia versus de valor

A característica mais importante que distingue esses 2 tipos está relacionada a como a igualdade e a atribuição/passagem de parâmetro são tratadas. A tabela abaixo mostra essas diferenças (supondo que os tipos em questão não sobrecarreguem o método Equals() ou os operadores ==/!=):


Value Type Reference Type
Assignment semantics
por valor, ou seja, o conteúdo da instância é copiado resultando em duas cópias independentes.
por referência, ou seja, a atribuição apenas copia uma referência e  mudanças através de qualquer uma das referências serão observadas ao acessar o objeto através da outra.
Equality
(Identity semantics)
duas instâncias são iguais se forem instâncias do mesmo tipo e todos os seus campos forem iguais. duas instâncias são iguais se referênciarem o mesmo objeto.

Para facilitar a visualização, imagine o seguinte código:

Durante sua execução, ao chegar na linha 15, podemos representar, de uma forma  simplificada, a memória utilizada para o armazenamento das variáveis v1 e v3 como algo:


ou seja, as variávis v1v3 são armazenadas nos endereços 0x100 e 0x400 respectivamente. Observe contudo que a variável v3 não armazena o contéudo da instância do objeto AReferenceType mas sim uma referência (ou, novamente em uma simplificação, um ponteiro) para o objeto instanciado no endereço 0x1000v1 (uma instância da estrutura  AValueType) por sua vez armazena os dados da instância diretamente (sem indireções).

Se inspecionarmos o estado da memória quando o programa executa a linha 19 observaremos algo como:

Observe que ambas as variáveis (v1 & v3) tem seus conteúdos copiados para as variáceis recem declaradas (v2 & v4) mas como v3 é uma refeence type, copiar seu valor implica copiar a referência (endereço) armazenada na mesma, fato que ficará mais evidente a frente.

Desta forma, ao imprimir os campos IntValue de v1 & v2 o mesmo resultado é produzido; contudo,  após armazenar o valor 71 em v2.IntValue (linha 21) o estado da memória pode ser representado como:


e consequentemente a linha 22 do programa imprimirá os valores 42 & 71 evidenciando que
v1 & v2 são independentes um do outro.

Reference types funcionam de uma forma diferente; ao executar a instrução na linha 25 o valor do campo IntValue das variáveis v3 e v4 são impressos. Para determinar quais valores serão passados para o método Console.WriteLine(), primeiramente o valor de v3 (0x1000) é lido da possição de memória 0x400 (v3) e a seguir, o conteúdo desta posição (0x1000) (ou mais precisamente 4 bytes que compõem um int em C#) é lido, resultando no valor 42; a seguir o memo processo é repetido para a variável v4 e como esta referencia o mesmo objeto que v3 (0x1000) o mesmo resultado é prodizido.

Uma vez que um processo similar é aplicado ao se modificar o conteúdo de um Reference Type, após a execução da linha 26 o estado da memória se parecerá com

 

e a linha 27 imprimirá 71 & 71, ou seja, modificações aplicadas ao objeto referenciado por v4 são observadas através da variável v3 (o que faz todo o sentido já que ambas variáveis referenciam o mesmo objeto).

Concepções equivocadas

Ao longo dos anos, li vários artigos, entrevistas e até alguns livros afirmando que uma das principais diferenças entre Reference Types e Value Types é que Value Types são sempre armazenados na pilha, enquanto Reference Types são sempre armazenados no heap.

Esse equívoco é tão difundido que Eric Lippert (o qual trabalhou como um dos designers de C# no passado) escreveu 2 postagens de blog com o objetivo de  acabar com a confusão.

Como Eric menciona, o fato de Value Type serem normalmente alocados na pilha é um detalhe de implementação. Contudo, na minha opnião este é um detalhe que dificilmente mudará uma vez que esse comportamento é essencial em cenários em que manter baixas as alocações de heap (e, consequentemente, a pressão de GC) é crucial.

Como sempre, todo feedback é bem-vindo.

Have fun!

Jun 30, 2023

Structs em C# - diversão garantida - Parte 1/9

Lisez cet post en french.

Read this post in English.

Mesmo existindo desde a introdução de .NET, Value types (e consequentemente, structs) possuem vários aspectos que ainda em 2023 são uma fonte de confusão (pelo menos para mim). Assim sendo decide fazer uma lista dos comportamentos que podem não ser tão óbvios, os quais abordarei em na série de futuro posts abaixo:

  1. Este post.
  2. Rápida introdução à Value Types vs Reference Types.
  3. Initialização de campos em estruturas.
  4. Comportamento de construtores em estruturas.
  5. Outros cenários em que o comportamento de construtores em estruturas podem te surpreender.
  6. Argumentos default  em construtores de estruturas (você ainda não esta confuso ?).
  7. Modificador `required` do C# 11 não vai salvar seu c* trabalho.
  8. Estruturas usadas como valor default de argumentos.
  9. Bonus: Evolução das estruturas em C#.

atualizando os links acima na medida que postar sobre cada assunto.

Antes de continuar, observe que esta série reflete o estado corrente do C#, ou seja,  a versão 11 (.NET 7.0).

Espero que a mesma possa ajudá-lo, mesmo se você for um desenvolvedor  C# experiente.

Como de praxe, todo retorno é bem vindo.

Have fun!

Jan 21, 2016

Encontrando referências para um assembly específico


Atualmente meu trabalho tem envolvido a manipulação / modificação de assemblies .NET, as vezes incluindo novos membros, outras mudando referências para tipos e/ou membros, etc.

Invariavelmente, de tempos em tempos acabo necessitando descobrir porque um assembly específico (vamos chamá-lo de A) possui uma referência para outro assembly (B); basicamente o que tenho que descobrir é quais tipos/membros do assembly B estão sendo referenciados (usados) pelo assembly A.

Existem várias formas de obter tal informação; na minha opinião, a mais simples, é converter, através do aplicativo 
 ildasm que vem junto com o .NET Framework, o assembly em questão (A) para IL e procurar por referências para o assembly B; para tanto você pode fazer algo como:

c:\temp> ildasm /all /out=c:\temp\assembly-a.il assembly-a.dll

Depois de gerar o IL do assembly desejado seu próximo passo é procurar pelo nome do assembly que você deseja encontrar referências (neste exemplo, Assembly_B); você verá uma ou mais (para ser mais preciso, uma entrada para cada assembly referenciado) 
sessões parecidas com:
.assembly extern /*23000005*/ Assembly_B
{

}

Agora você pode tanto procurar pelo nome do assembly (Assembly_B) ou pelo número que se encontra à sua esquerda (neste exemplo 23000005)

.assembly extern /*23000005*/ Assembly_B
{
  .ver 1:0:0:0
}
.
.
.

IL_0006:  /* 28   | (0A)0000B2       */ call instance void [Assembly_B/*23000005*/]NamespaceName.TypeName::DoSomething()
.
.
.


Como você pode notar no trecho de IL acima, no offset 06 do IL existe uma referência ao método DoSomething() da classe TypeName, definida no namespace 'Namespace' no assembly Assembly_B.

Happy coding.

(read this post in english)

Mar 5, 2014

Executando scripts em documentos do google drive

(If you don't read Portuguese, you can read a simplified version of this post in english)

Ola.

Sempre tive certo "desdém" por soluções de edição de documentos (principalmente planilhas) nas "nuvens" (tipo google drive e/ou office 365), motivado, principalmente, por acreditar que as mesmas não me permitiriam incluir meus tão úteis scripts (afinal de contas, desenvolvedor que se prese tem que usar scripts em tudo ;)

Mesmo assim, ha algum tempo venho usando uma planilha no google drive para controlar ordens de pagamento; acontece que, na semana passada eu estava considerando passar a usar o LibreOffice (que por sinal eu recomendo) para fazer minhas edições nesta planilha pois eu queria modificar a cor de uma das células caso uma determinada condição fosse satisfeita (e isso, é claro, é tarefa para scripts). 

Não sei porque (ou como) eu acabei descobrindo que esta minha resistência em usar o google drive não passava de puro preconceito! Se você for no menu "Tools" (deve ser "Ferramentas" no bom e velho português) encontrará a opção "Script Editor"! Ou seja, pelo menos as planilhas do google drive, suportam scripts! E o melhor, até dupuração!)

Pronto! É tudo que eu precisava; depois de alguns minutos (e um punhado de teclas pressionadas) meu primeiro script estava pronto. E sabe da melhor? É possível referenciar scripts em fórmulas ou até mesmo agendar o mesmo para executar de tempo em tempo (exatamente o que eu buscava).

Abaixo segue o código que usei:
function checkDate() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheets()[0];       
    
  var range = sheet.getRange("A1");
  
  var now = new Date();
  
  if (now.getHours() <= 10 || (now.getHours() >= 15 && now.getMinutes() > 30) || (now.getHours() >= 13 && (now.getHours() <= 14 && now.getMinutes() < 30)) ) {    
    range.setBackground("ORANGE");
    range.setValue("FECHADO");
  }
  else {
    range.setBackground("GREEN");
    range.setValue("ABERTO");
  }
}
Hoje eu encontrei mais um motivo para eu criar mais um script nesta planilha: sempre que recebo ordens de pagamento eu quero saber a cotação do dolar no momento. Normalmente o que eu fazia era abrir o browser e navegar para algum site de cotação. 

O problema com esta solução é que a mesma é ...  digamos.... é manual (e não usa scripts :)). A solução mais óbvia para a questão? Encontrar alguma empresa que disponibilize a cotação do dolar através de um WebService e escrever um script para chamar este WebService.

Para minha surpresa, ao fazer uma busca na web, encontrei este post que descreve como acessar o WebService do Banco Central do Brasil (que eu nem imaginava que existisse) para obter tal informação. Perfeito, agora eu só tive que descobrir uma forma de executar o mesmo e fazer o parse dos dados retornados (o que não foi tão simples quanto parecia pois não consegui encontrar documentação do WebService em lugar algum).

O Código final ficou mais ou menos assim (não me preocupei em fazer tratamento de erros, muito menos em ser eficiente, só queria que o mesmo funcionasse :)
function getDolarExchangeRate() {
  Logger.clear();
  
  var wsdl = SoapService.wsdl("https://www3.bcb.gov.br/sgspub/JSP/sgsgeral/FachadaWSSGS.wsdl");  
  var service = wsdl.getService("FachadaWSSGSService");  
  var result = service.invokeOperation("getUltimosValoresSerieVO", ["1", "1"]);
    
  var i = 0;
  while (typeof result.Envelope.Body.multiRef[i] != 'undefined' ) {
    if (typeof result.Envelope.Body.multiRef[i].svalor != 'undefined')
      return result.Envelope.Body.multiRef[i].svalor.getText();
    i++;
  }
  
  return "Could not find 'svalor' element";
}
Agora tudo que tive  que fazer foi escolher uma célula na planilha e entrar a formula =getDolarExchangeRate() na mesma e bum, lá estava a cotação do dolar.

Happy Coding!

Feb 26, 2014

Utilitário útil da semana: Clip

Se você é um usuário do Windows um pouco mais avançado e utiliza o console (cmd.exe) é provável que recorra ao menu de contexto quando necessita copiar qualquer conteúdo do console para a área de transferência (clipboard) do Windows.  

Você sabia que o Windows (pelo menos a partir do Windows  7) vem com um utilitário chamado clip que permite que você copie qualquer conteúdo passado para sua entrada padrão (stdin) para a área de transferência? Por exemplo você pode copiar o resultado de um dir para o clipboard com a seguinte linha de comando:


c:\temp>dir | clip

É claro que você também pode redirecionar a entrada padrão:

c:\temp>clip < arquivo.txt
Não sei quanto a você, mas quando eu descobri este pequeno utilitário minha vida no console ficou muito mais fácil.

(Este post em Inglês)

Oct 26, 2013

Closures em C# - Resposta


No post anterior eu deixei como pergunta qual seria a saída de um programa simples em C#:

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
   private static void Main(string[] args)
   {
      foreach (var func in GetFuncs1())
      {
         Console.WriteLine("[first] {0}", func());
      }

      foreach (var func in GetFuncs2())
      {
         Console.WriteLine("[second] {0}", func());
      }
   }

   private static List<Func<int>> GetFuncs1()
   {
      var fs = new List<Func<int>>();

      for (int i = 0; i < 3; i++)
      {
         fs.Add(() => i);
      }
      return fs;
   }

   private static List<Func<int>> GetFuncs2()
   {
      var fs = new List<Func<int>>();

      foreach (var i in Enumerable.Range(0, 3))
      {
         fs.Add(() => i);
      }
      return fs;
    }  
}

A resposta para a pergunta é: depende.

(antes de continuar quero ressaltar que toda vez que me referir a um laço for/foreach, assuma que o mesmo se encontra no contexto de lambda expressions / métodos anônimos que capturam variáveis locais / parâmetros)

Para que você possa enterder o problema melhor é necessário antes entender um pouco sobre closures e a sua relação com o uso de variáveis (locais / parâmetros). Se você precisar refrescar a memória recomendo a leitura deste blog post e também deste outro).

Basicamente ambos os métodos do exemplo (GetFuncs1 e 2) criam lambda expressions as quais em seus corpos referenciam a variável local (i)(linhas 26 e 37); para evitar que a área de memória reservada para tal variável seja reutilizada pela CLR o compilador C# "captura" a variável (você pode utilizar o ILDASM ou ILSpy para verificar o código IL gerado e assim entender melhor este processo) e é aqui que os nossos "problemas" começam.

Observe que eu disse que o compilador "captura" as variáveis, não seus valores no momento da criação das lambda expressions; assim, supondo que tivessemos apenas a chamada ao método GetFuncs1(), o código resultante seria equivalente ao código abaixo (note as linhas marcadas: 20, 25-28 e 30) (o código gerado na realidade é bem diferente mas para nossos propósitos podemos tratá-lo como equivalente):

using System;
using System.Collections.Generic;

class Program
{
 private static void Main(string[] args)
 {
  foreach (var func in GetFuncs1())
  {
   Console.WriteLine("[first] {0}", func());
  }
 }

 private static List<Func<int>> GetFuncs1()
 {
  var fs = new List<Func<int>>();

  for (ret = 0; ret < 3; ret++)
  {
   fs.Add(ret_func);
  }
  return fs;
 }

 private static int ret_func()
 {
  return ret;
 }

 private static int ret;
}
Como vemos, ao invés de criar um método para representar o corpo das lambda expressions em cada iteração do laço, o compilador emitiu um único método estático (ret_func() inserindo referências para o mesmo na lista de funções) que simplesmente retorna o valor do campo ret o qual, por sua vez, é atualizado no laço for!

Agora que você já entende porque a saída do programa foi 3,3,3 restam, pelo menos, duas questões:

  1. Como obter o comportamento desejado, ou seja, que a saída do programa seja 0, 1 e 2 ?
  2. Porque eu disse que a saída do programa "dependia" de algo, e o mais importante, depende do que?
A resposta para a primeira pergunta é sim; (veja o programa abaixo) basta declarar uma variável local dentro do laço for e atribuir a variável "i" para a mesma (linha 20) e, então, retornar o valor desta variável no corpo da lambda expression (linha 21) :
using System;
using System.Collections.Generic;

class Program
{
 private static void Main(string[] args)
 {
  foreach (var func in GetFuncs1())
  {
   Console.WriteLine("[first] {0}", func());
  }
 }

 private static List<Func<int>> GetFuncs1()
 {
  var fs = new List<Func<int>>();

  for (int i = 0; i < 3; i++)
  {
   var capture = i;
   fs.Add(() => capture);
  }
  return fs;
 }
}
Se você executar o programa agora verá que a saída do mesmo é exatamente a esperada; isto ocorre porque, agora, como declaramos uma variável local dentro do laço o compilador irá emitir código equivalente ao código abaixo (efetivamente capturando o valor da variável no momento em que a lambada expression é criada):
using System;
using System.Collections.Generic;

class Program
{
 private static void Main(string[] args)
 {
  foreach (var func in GetFuncs1())
  {
   Console.WriteLine("[first] {0}", func());
  }
 }

 private static List<Func<int>> GetFuncs1()
 {
  var fs = new List<Func<int>>();

  for (int i = 0; i < 3; i++)
  {
   var h = new Holder {value = i};
   fs.Add(h.GetIt);
  }
  return fs;
 }

 class Holder
 {
  public int value;

  public int GetIt()
  {
   return value;
  }
 }
}
Novamente ficou mais simples entender porque este código produz o resultado esperado: em cada iteração do laço o compilador instanciou um objeto para armazenar o valor de "i" naquele momento.

Estou certo de que, de agora em diante, toda vez que você utilizar lambda expressions / métodos anônimos você irá certificar-se de usar a construção compatível com o resultado desejado.

Quanto à segunda questão, se este blog tiver um número razoável de leitores, teremos dois grupos com resultados diferentes: 3,3,3 / 2,2,2 e 3,3,3 / 0,1,2 (que é a saída esperada)!

A diferença nos resultados esta relacionada à versão do compilador C# utilizado.

Veja a imagem abaixo:

A versão 5 do C# mudou o comportamento de variáveis capturadas em laços foreach! A partir desta versão o compilador emite código como se a cada iteração uma nova variável local fosse alocada (o mesmo truque que eu apresentei acima);

Agora eu me questiono porque o comportamento de laços for não foram modificados também. O que posso dizer é que, na minha opinião, esta decisão só introduz confusão. Desenvolvedores iniciantes na linguagem irão, invariavelmente, ser surpreendidos por esta diferença de comportamento.

Caso estes desenvolvedores tenham contato primeiramente com laços for os mesmos ficaram tentando entender porque suas lambdas expressions/métodos anônimos estão observando uma valor atualizado da variável capturada e, então, quando eles entenderem o que esta ocorrendo tenderão a usar o truque de "declarar uma variável local dentro do laço" (inclusive em laços foreach). Por outro lado, caso estes desenvolvedores tenham contato com o laço foreach primeiro, eles correm o risco de em algum momento usar um laço for e introduzir bugs (uma vez que os mesmos assumirão que o valor da variável do laço será capturada).

Há uma discussão sobre o assunto que, ainda que eu compreenda os argumentos, não concordo completamente com os mesmos - partes do meu cérebro ainda acham que esta diferença no comportamento é uma inconsistência gratuíta - mas esta é apenas minha opinião.

Se você estiver interessado em mais detalhes sobre o assunto recomendo a leitura do capítulo 8.8.4 da especificação da linguagem C#.

Finalmente um alerta: se você for portar projetos de uma versão do C# anterior à 5.0 para a versão 5.0 ou mais nova preste atenção na combinação lambda expressions / laços for pois como vimos é possível ocorrer mudanças de comportamento.

O que você acha?

(read this post in english)

Sep 17, 2013

Utilitário útil da semana: ConEmu

Se você é um usuário avançado do Windows provavelmente recorre ao console (cmd.exe) para executar várias das suas tarefas diárias. Eu, em geral, tenho 2 ~ 3 consoles abertos normalmente e este fato é exatamente a motivação deste post.

Vamos ser sinceros: se você já usou algum shell (tipo bash) que acompanha uma instalação Linux moderna usar o console do Windows dá vontade de mudar para o Linux ;)


Felizmente não é necessário uma mudança tão radical (muito embora eu recomendo usar o Linux, ou qualquer outro S.O. sempre que possível). Por exemplo, a algum tempo eu vinha usando o console2 que é uma boa alternativa ao console do Windows. Recentemente eu passei a usar o ConEmu simplesmente porque o mesmo tem sido atualizado mais frequentemente que o console2 (de qualquer forma eu recomendo instalar ambos e experimentar para ver qual lhe agrada mais).

Entre outros recursos o ConEmu suporta múltiplas tabs, seleção de texto muito melhorado, uso de qualquer fonte, etc.

Quanto a mim, não pretendo voltar a usar o console do Windows (talvez, quem sabe, usar mais o power shell, mas isto já é assunto para outro post :).

(This post in English)

Sep 4, 2013

Posts em português

Olá,

Quando eu comecei a postar em meu primeiro blog eu postava em português; contudo depois de algum tempo decidi postar em inglês. Meu objetivo era o de me forçar a escrever neste idioma e assim aperfeiçoar minhas habilidades com o mesmo.

Contudo após este tempo eu repensei a decisão e concluí que não postar em português estimularia esta tendência de adotar o inglês em detrimento do Português. Assim sendo, a partir deste post, pretendo seguir um modelo em que postarei nos dois idiomas (contudo o conteúdo dos posts não serão uma tradução literal de um idioma para o outro).

O que você acha? Após alguns posts vou reavaliar e decidir se continuo neste modelo, se volto a postar apenas em inglês, se passo a postar apenas em português ou, quem sabe, se paro de postar :)

PS: Aqui esta a versão em inglês deste post.

Happy coding!