Skip to main content

C# 2.0 - Generics

Este é um assunto já bastante discutido pela internet fora. Todos sabemos o que são Generics, e para que são usados, certo? Não. Existe uma variedade de razões pelas quais o leitor pode não saber o que são Generics, e algumas delas são:
  • O leitor é novo na linguagem,
  • O leitor não é novo na linguagem, mas nunca se deu ao trabalho de tentar aprender.
Se o leitor nunca aprendeu generics, isso não significa que nunca terá de aprender. Se reconhece este snippet, então saiba que já estamos a lidar com generics.

Portanto, qual é a vantagem de utilizar generics?

Type safety
Generics permite-nos apanhar erros na compilação em vez de em runtime, obrigando o programador a respeitar as regras de type safety que fazem do C# uma linguagem estática. Isto leva-nos a uma produtividade acelerada porque não temos que correr o programa para saber que cometemos um erro. Por exemplo, se no snippet acima tentássemos adicionar um novo item do tipo string, o compilador daria erro.

Tipicamente, no C# 1.0, podiamos usar uma colecção ArrayList para conter uma lista de itens, independentemente do tipo desses itens.  Penso que nunca precisei de mais do que um tipo na mesma colecção, mas uma ArrayList dá-nos essa liberdade. Ao dizermos o tipo que queremos na nossa List, o compilador vai criá-la internamente e vai reconhecer que naquela instância apenas podemos ter int's. Por exemplo:



O mesmo aconteceria para o Add, Remove e todos os outros métodos que utilizam generics da List. Mas já iremos ver mais sobre isso.

Performance
Performance é uma das vantagens mais importantes ao utilizar generics na situação acima descrita. Quando adicionamos um item a uma ArrayList, estamos a fazer uma operação de boxing (o que significa que estamos a criar um objecto especial e a meter o nosso valor lá dentro), e quando vamos buscar o valor temos que fazer uma conversão para o nosso tipo específico e fazer uma operação de unboxing. Estas operações podem tornar-se pesadas quando as fazemos múltiplas vezes, tal como atravessar a colecção inteira.

Ao dizermos à colecção qual o tipo com que está a lidar, evitamos esse inconveniente de ter que converter o nosso valor para o utilizar, e isso irá dar-nos um ganho de performance. Claro que a diferença apenas se torna visível quando estamos a lidar com colecções grandes, mas está lá.

Reutilização do código
Penso que esta é a maior vantagem de utilizarmos generics como ferramenta. Quando lidamos com colecções de itens no C# 1.0, se quisermos ter os benefícios acima explicados teríamos de criar a nossa própria colecção. Imaginem todo o trabalho em criar manualmente uma colecção para a classe Pessoa, Barco, Carro, etc. Ao invés disso, podemos utilizar generics para esta tarefa. List(of T) faz exactamente isso, portanto vamos ver a sua declaração.


A única coisa estranha aqui é o T que vem após o nome da classe. Isto é o que nos permite passar o type com que estamos a lidar e chama-se um type parameter. Dentro da nossa classe genérica, utilizamos T (ou qualquer nome que lhe queiramos dar) para nos referirmos ao tipo que o código cliente nos forneceu por parâmetro. Quando criamos uma List(of T), o compilador vai criar uma classe da mesma forma que nós faríamos manualmente. Isto significa que podemos reutilizar o nosso código muito mais facilmente. 

Vamos explorar a capacidade dos generics criando uma colecção nossa:

Como podemos ver, este não é um conceito difícil de aprender. Criamos uma nova classe MinhaLista que vai receber um type parameter chamado T. Podemos ver que temos uma propriedade chamada UltimoItem do tipo T a representar o último item adicionado à nossa colecção. Temos também um método que recebe um T e o devolve após adicionar à nossa colecção interna e disparar o evento ItemAdicionado. Este evento também utiliza o type argument T, e um delegate genérico incluído no .NET que nos permite especificar de forma genérica o tipo do seu segundo parâmetro.

Um dado importante na nossa implementação é que nós estamos a utilizar generic constraints. Estas são representadas pelo where T : Pessoa que podemos ver na declaração da classe. Quando utilizamos constraints, o código cliente vai apenas conseguir passar um tipo que implemente as interfaces ou seja herdeiro das classes especificadas nas constraints. Se Pessoa for uma classe então conseguiremos apenas passar o tipo Pessoa ou um tipo mais derivado. Dentro da nossa classe genérica passamos então a ter acesso aos membros na classe Pessoa, porque temos a certeza que estamos a lidar com esta classe. São elegíveis para generic constraint os seguintes:
  • Qualquer classe
  • uma interface
  • As palavras reservadas class e struct
  • outro type parameter 
  • new()  Isto permite-nos especificar que o tipo tem que ter um construtor public e sem parâmetros.
Esta é a forma como iríamos utilizar a classe MinhaLista:

Podemos ver que utilizamos a nossa colecção combinada com o tipo Pessoa. Após especificarmos o tipo, o compilador tem toda a informação para criar a classe e analisar as chamadas. O handler do evento (Pessoa_Adicionada) é chamado com o segundo parâmetro do tipo Pessoa, como foi especificado na declaração do evento. O método  AdicionarEDevolverOMesmo irá receber e devolver uma Pessoa.

Também podemos ter um método genérico numa classe não genérica da seguinte forma:


Reparem que estamos a especificar um type parameter quando declaramos o método. Ao chamar o método não é necessário incluir o type parameteri, porque o compilador consegue inferi-lo através do argumento do método. Temos também a opção de especificar constraints para estes parâmetros, tal como na declaração de uma classe.

Quando usar
Um novo namespace (System.Collections.Generic) foi adicionado no C# 2.0 especialmente para as colecções genéricas. List(of T) é uma das mais usadas, mas também temos o Dictionary(of TKey, of TValue) que podemos usar praticamente da mesma forma que uma Hashtable (mas com os benefícios acima descritos acima) e também toda a estrutura base que suporta estas e outras colecções genéricas.

Todo o conceito de generics é mais direccionado para colecções no geral, mas isso não é obrigatório. Podemos utilizá-los obviamente onde quisermos, incluindo classes, interfaces, métodos ou mesmo delegates.  Utilizar generics vai trazer mais flexibilidade ao nosso código, mas pode também aumentar o grau de dificuldade em compreendê-lo se abusarmos. Ainda assim, existem situações onde já os apliquei com bastante sucesso. O exemplo a que me refiro era um User Control que, quando clicado, criava um Form de popup. Utilizar generics permitiu-me reutilizar o código apenas especificando algumas constraints. Desta forma, o User Control nunca teria que saber o tipo do Form, mas podia instanciá-lo na mesma. Apliquei aqui um pouco o conceito do strategy design pattern e funcionou muito bem.

A palavra reservada default
A palavra default tem um objectivo muito bem definido no código genérico. Recebe um parâmetro que é o nome de um tipo e devolve o valor por defeito desse mesmo tipo. Portanto, se passarmos uma classe, irá retornar null, se passarmos uma struct irá retornar uma struct com todos os campos inicializados para o seu valor por defeito, e por aí em diante... Vejamos o snippet: 


O meu conselho é: aprendam bem esta feature do C# 2.0, porque eventualmente vai dar frutos!

Obrigado.

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?

The evolution of C# - Part III - C# 2.0 - Iterators

It's been a while since i wrote the last post, but i did not forget my purpose of creating a series that shows the evolution of C#. Today i came here to talk about one of the most useful features of C#, even if you dont know you're using it. Let's talk about iterators ! What is an iterator? For those of you who didn't read about the iterator pattern somewhere in the internet or in the "Gang of Four" book, you can read a description  here . The iterator is a class/object/whatever which knows how to traverse a structure. So, if you have a list or collection of objects, an iterator would have the knowledge of how to traverse that collection and access each element that it contains. The iterator is a well known design pattern and is behind many of the wonderful that we have nowadays in .NET (Linq comes to mind). Why is it a feature? Truth be told, an iterator is a concept well known way before .NET even existed. Being an OO Design Pattern, the iterator has

My simplest and most useful type

I have been doing some introspection on the way I write code to find ways that I need to improve. I consider this a task that one must do periodically so that we keep organized. There is a very, very simple problem that occurs in every application I know: How to return the results of an operation to the user? I've seen many implementations. Some return strings, some throw exceptions, some use out parameters, reuse the domain classes and have extra properties in there, etc. There is a myriad of ways of accomplishing this. This is the one I use. I don't like throwing exceptions. There are certainly cases where you have no choice, but I always avoid that. Throughout my architectures there is a single prevalent type that hasn't changed for years now, and I consider that a sign of stability. It is so simple, yet so useful everywhere. The name may shock you, take a look: Yes, this is it. Take a moment to compose yourself. Mind you, this is used everywhere , in every