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)

Oct 5, 2013

Closures in C#


Hi

Given the following code:

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;
    }  
}

What do you expect as the output? (try answering before running it)

Does it produces the results you expected? No? Do you understand why?

(If you are using Resharper it will warn you that you wrote code that captures variables and can produce "unexpected" results)

In the next post I'll give the answer and discuss it.

Happy coding.

(leia este post em Português)

Closures em C#


Olá.

Dado o seguinte programa em C# o que você espera que o mesmo imprima? (tente responder a pergunta sem compilar/executar o mesmo):

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;
    }  
}


Se você rodar o mesmo, o resultado obtido é compatível com sua expectativa? Não? Você entende o porque?

No próximo post vou discutir o comportamento deste programa e também como evitar algumas "armadilhas" relacionadas a Closures em C#.

(read this post in english)

Sep 17, 2013

Useful utility: ConEmu

Have you ever used cmd.exe (a.k.a Windows Console)? In my day to day tasks usually I have from 2 ~ 3 consoles open, which brings me to this post.

Lets be fair: Anyone that has used any *nix based shell (bash comes to my mind), usually considers (seriously) moving to Linux or some *nix based OS ;)

Even though I do recommend using a different OS, such a radical change is not required all you want is a better experience when using a console; on Windows, there are some alternatives to cmd.exe; for instance, I have used console2 for some time and recently I switched to ConEmu (mostly because it has been updated more frequently); in any case I do recommend you to take the time to check both to see which one you adapt better, if any :).

Since I started using such console replacements I don't miss cmd.exe even for a second ;)

(Este post em Português)


Happy coding.

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

Blogging experiments....

Hi,

Even though in the beginning I used to post in portuguese (my native language) I decided to switch to english to force me to write more in this language.

Although I am still firm believer that anyone interested in information technology field do need to be able to read english I came to the conclusion that posting in portuguese is also valuable so, starting with this post I'll experiment posting in both english / portuguese.

What do you think? 

If I get no feedback then I may decide switching back to english only posts :).

(you can read this post in portuguese)

Happy coding!

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!