Skip to main content

A evolução do C# - O início

Hoje vamos falar sobre História. Não aquela História das Guerras Mundiais, mas a história da evolução do C# como uma linguagem. Tenho em mente analisar as partes principais de uma das linguagens de programação em massa mais utilizadas no mundo. A grande parte do que vou falar é fruto de muita pesquisa, em parte porque quando o C# e o .NET foram disponibilizados eu ainda não tinha escrito uma única linha de código, por isso podemos dizer que sou novato neste mundo.

No entanto, estou a esforçar-me por mudar isso, e cada nova descoberta é feita com muito entusiasmo da minha parte, e eu considero-me extremamente pro-activo, o que poderá até irritar quem está à minha volta. Uma das razões pelas quais estou a escrever este post, e já agora todo este blog, é porque eu tenho gosto em aprender coisas novas e principalmente partilhá-las com outros. Chega de conversa, vamos ao que interessa!

O início do .NET

A plataforma .NET traz uma nova forma de programar as nossas aplicações. A Microsoft criou o termo "Managed Code" para se referir a código que corre no seu runtime chamado CLR (Common Language Runtime). O CLR é responsável por interpretar o que o nosso compilador de C# (ou VB, ou outra linguagem .NET) emite na forma de IL (Intermediate Language - IL). Este esquema foi retirado da Wikipedia e mostra-nos como como isto funciona.


Basicamente, o código que escrevemos é compilado para IL pelo compilador de C# e depois, quando corremos o programa, o JIT Compiler traduz essa IL para código nativo entendido pelo processador. Isto acontece enquanto corremos o nosso programa e tem o seu custo, como Eric Lippert explica neste artigo. 

Isto é tudo muito bonito, mas quais as vantagens reais do Managed Code?

IL
Para começar, a IL é uma vantagem em si. Compilar para uma linguagem comum significa que uma nova linguagem poderá ser adicionada à família .NET mais facilmente. IL representa uma abstracção no sentido em que tudo o que teríamos de fazer seria fazer o novo compilador emitir a IL correcta. Claro que isto é um exemplo extremamente simplista, mas a Microsoft já provou que é verdade criando uma família numerosa de linguagens de programação para a família .NET.

Para uma lista detalhada de linguagens que suportam este conceito de emitir IL, vejam este artigo. O número e a natureza variada de linguagens (desde linguagens Orientadas a Objectos até Funcionais) prova o quão versátil o conceito realmente é. 

Segurança
Penso que neste aspecto a coisa mais importante a notar é que, sendo uma plataforma, o CLR sabe o que está a correr nele. Existem mecanismos de segurança no CLR para garantir que o código é seguro e age como um supervisor. São coleccionados meta-dados associados com os assemblies carregados chamados Evidences. Neles, são contidas informações sobre o assembly, tal como quem publicou, a directoria da aplicação, etc. Tudo isto é analisado e são dados acessos ao código. Esta é uma explicação muito superficial, mas eu não sei nenhum dos detalhes intrínsecos neste assunto,  que poderão consultar aqui.

Base Class Library
Esta é a parte interessante. A BCL representa todas aquelas classes e estruturas básicas que são a base da .NET.Framework. Eu digo .NET porque a BCL é partilhada entre a família .NET. Isto significa que as mesmas classes, métodos, etc disponíveis no C# estão também disponíveis no VB. O objectivo principal desta abordagem é fazer com que o nosso trabalho seja centralizado enquanto programadores e dar-nos consistência dentro da plataforma. Assim, conseguimos encontrar uma abordagem unificada para interagir com partes mais baixas do sistema tal como escrever para Streams, interagir com o OS, e ter todas as classes básicas juntamente como suporte para muitas das features de toda a .NET Framework. Está também padronizada de acordo com os padrões ECMA e disponível para outras implementações, tal como o Mono.
    
Gestão de memória (Garbage Collector).
Esta é outra vantagem sem a qual eu nunca vivi, portanto não sei o que dizer quando me perguntarem como era o mundo antes. Mas vou tentar. Quando criamos um objecto, esse objecto vive na memória do nosso computador. Esse objecto está agora ao alcance do nosso código, e enquanto assim for ele não será destruido pelo Garbage Collector (GC). 

Apenas consigo imaginar como seria a vida antes de haver GC. Imagino que seja algo complicado, gerir a memória ocupada. Imaginemos que tínhamos ter em conta cada variável e preocupar-nos em dizer ao sistema que já não o estávamos a utilizar e que a memória podia ser reutilizada. Tenho a certeza que era uma tarefa complexa, aborrecida e também muito, muito propensa a erros. Consigo também imaginar que um grande número de aplicações sofra de memory leaks sem o saber, acabando por descobrir da pior forma.

Então, uma das ferramentas que o GC utiliza para limpar a memória é o conceito de scope. Scope indica se um determinado objecto está ou não acessível para o nosso código e pode ser destruido. Não pensem que um objecto é destruído assim que este sai de scope. Na realidade, o GC tem a sua própria forma de actuar, e não é possível determinar facilmente quando um ciclo de destruição vai acontecer. As razões são muitas: pode ser porque a memória está sob pressão, porque a Geração 0 do GC está esgotada...

Em relação a Gerações, quando o GC corre, ele começa por procurar estrategicamente em objectos base e depois recursivamente marca cada referência desses objectos como dentro de scope. Quando um objecto não está no scope, é marcado para destruição e o seu método Finalize é chamado. Após isso, a memória é libertada, mas os objectos que não foram destruidos são promovidos para a próxima Geração. Temos a Geração 0, 1 e 2. Na Geração 2 é onde estão os objectos que sobreviveram a duas passagens do GC e é menos provável que venham a ser destruídos. Estamos maioritariamente a falar de objectos static, variáveis globais e etc.

Portanto, o GC existe para tornar a nossa vida mais fácil, por isso estamos gratos que ele exista. Não temos de nos preocupar em libertar memória, apenas com o meu programa. Como é óbvio, precisamos de nos preocupar em limpar referências não necessárias dos nossos objectos, mas esse é um trabalho bem mais fácil. Para matar este assunto, basta-me fazer notar que esta foi uma introdução muito, muito superficial e que poderão ler mais aqui.

Segurança
Quando falo de segurança aqui, estou basicamente a dizer que estamos seguros de todos os conceitos de baixo nível com que teriamos de lidar em C, tal como pointers, acesso directo à memória... Quando criamos um objecto no heap, é-nos dada uma referência abstracta para esse objecto na memória e nunca o endereço daquele bloco em específico. Exceptions são outro tipo de segurança que obtemos do managed code. Quando ocorre um erro, podemos reagir-lhe e ter toda a informação sobre ele na nossa IDE. Em unmanaged code é um pouco mais difícil lidar com isto, e por vezes o programa sai sem qualquer aviso do porquê.

Static typing é outra das grandes vantagens do .NET. O compilador faz inúmeras verificações e com isso poupa-nos bastante tempo ao impedir a execução do programa ao invés de simplesmente o correr com erros. São verificados os types das variáveis, a declaração dessas mesmas variáveis e o cumprimento de inúmeras regras da linguagem mesmo antes de completarmos a build.

Produtividade
Não poderia acabar o artigo sem falar neste ponto. Este é provavelmente um dos efeitos secundários mais importantes da introdução do .NET. As linguagens da família .NET são muito produtivas, fáceis de aprender e, claro, sustentadas por muitas libraries que dão vida ao mundo do .NET. Ao longo dos anos, tornou-se extremamente apreciado em todo o mundo e assistimos a uma explosão nas metodologias e ferramentas que muitas empresas estão a desenvolver. A tecnologia evoluiu ainda mais, e hoje em dia tornou-se uma das mais importantes plataformas de desenvolvimento. Em C#, por exemplo, temos Generics, LINQ, Lambdas, Async e mais para vir. Nos próximos artigos irei falar dessas evoluções.

Para lá disto, temos uma das IDE's mais poderosas, extensíveis e versáteis do mundo. Estou a falar, claro, do Visual Studio. Apesar de serem independentes e ser possível utilizar outras IDE's, não poderia deixar de parte a ferramenta de trabalho que evoluiu especificamente para nos dar todo o potencial da plataforma.

Desvantagens
Managed code trouxe também algumas desvantagens consigo, como muitas pessoas inteligentes fazem notar. Velocidade é uma das desvantagens mais debatidas. Claro que ter uma linguagem compilada directamente para assembler é bastante mais rápido do que ter uma máquina virtual a interpretar IL. Este é um factor significante a ter em conta ao desenvolver sistemas de baixo nível tais como sistemas operativos ou drivers de baixo nível. Para a maioria das aplicações, é irrelevante porque no fim o que acaba por interessar é que o utilizador final tenha uma experiência fluída..  

Outra desvantagem é que o desenvolvimento multi-plataforma não é directamente suportado pela Microsoft, mas existem outras implementações multi-plataforma da .NET Framework, tal como o Mono.

Nos próximos capítulos vamos ver código real e a evolução que foi feita do C# 1.0 para o C# 2.0. Serão separadas as mudanças mais importantes por post, para poder entrar em detalhe em cada uma.

Obrigado a todos por lerem!

Comments

Popular posts from this blog

The repository's repository

Ever since I started delving into architecture,  and specifically service oriented architecture, there has been one matter where opinions get divided. Let me state the problem first, and then take a look at both sides of the barricade. Given that your service layer needs to access persistent storage, how do you model that layer? It is almost common knowledge what to do here: use the Repository design pattern. So we look at the pattern and decide that it seems simple enough! Let's implement the shit out of it! Now, let's say that you will use an ORM - here comes trouble. Specifically we're using EF, but we could be talking about NHibernate or really any other. The real divisive theme is this question: should you be using the repository pattern at all when you use an ORM? I'll flat out say it: I don't think you should... except with good reason. So, sharpen your swords, pray to your gods and come with me to fight this war... or maybe stay in the couch? ...

Follow up: improving the Result type from feedback

This post is a follow up on the previous post. It presents an approach on how to return values from a method. I got some great feedback both good and bad from other people, and with that I will present now the updated code taking that feedback into account. Here is the original: And the modified version: Following is some of the most important feedback which led to this. Make it an immutable struct This was a useful one. I can't say that I have ever found a problem with having the Result type as a class, but that is just a matter of scale. The point of this is that now we avoid allocating memory in high usage scenarios. This was a problem of scale, easily solvable. Return a tuple instead of using a dedicated Result type The initial implementation comes from a long time ago, when C# did not have (good) support for tuples and deconstruction wasn't heard of. You would have to deal with the Tuple type, which was a bit of a hassle. I feel it would complicate the ...

C# 2.0 - Partial Types

For those of you interested, i found a very interesting list of features that were introduced in C# in  here . This is a very complete list that contains all the features, and i'm explaining them one by one in this post series. We've talked about  Generics  and  Iterators . Now it's time for some partial types . A partial type  is a type which definition is spread across one or more files. It doesn't have to be in multiple separated files, but can be. This is a very simple concept that can give us many benefits, let's see: If a type is partial, multiple developers can work on every part of it. This allows a more organized way of working and can lead to production improvement.  Winforms , for example, generates a partial class for the form so that the client can separately edit other parts it. This way, a part contains information about the design and the other contains the logic of the form. In fact, this is a very spread pattern across .Net. Ent...