quarta-feira, 7 de setembro de 2011

List ou IList: eis a questão

Este texto é uma tradução do post que escrevi para o blog Dive Into Code.


Em um dia de reflexão durante uma conversa no trabalho, surge uma questão: Por que tantos desenvolvedores utilizam sempre referência para List<T> no lugar de IList<T> em C#? Mas a resposta convincente para esta pergunta não apareceu. Dessa forma o princípio de depender de abstrações e não de implementações concretas estaria sendo desprezado.


Aparentemente não é a primeira vez que esta dúvida aparece. E é possível notar que boa parte dos desenvolvedores que usam List<T> no lugar de IList<T> já foram programadores Java um dia. Seria este o motivo?


Em C# costuma-se utilizar a letra “I” no início do nome de interfaces. Pessoas que já programaram em Java poderiam se confundir, uma vez que List em Java representa a interface e LinkedList e Stack seriam exemplos de implementações. E é importante notar que, em C#, LiskedList<T>, Queue<T> e Stack<T> não implementam IList<T>, pois não fornecem acesso por índice.


Torna-se necessário entender mais a classe genérica List<T>, e as interfaces que ela implementa (IEnumerable<T>, ICollection<T>, IList<T>, IEnumerable, ICollection e IList). Assim, tais interfaces poderiam ser utilizadas como referência no lugar da classe concreta.


Uma vez que foi entendido um pouco mais sobre as interfaces e o que elas forneciam em termos de funcionalidade, era necessário fazer as alterações em algum projeto que fosse utilizado sempre List<T>. No primeiro momento, substituindo ao menos o retorno de um método de List<T> para IEnumerable<T>. Com esta mudança, apareceram alguns erros de compilação, faltava o método Add(T). Porém com ICollection<T> o código funcionou normalmente. Em alguns outros casos o IEnumerable<T> atendia a necessidade, pois só era necessário percorrer os ítens da lista retornada.


Mais adiante, foi encontrada uma situação em que era necessário acessar um ítem específico da estrutura, que foi resolvido utilizando a interface IList<T> como retorno. Até que apareceu um AddRange(IEnumerable<T>) de List<T> sendo utilizado, que poderia ser facilmente substituído por um extension method para ICollection.


Mas depois de todas as alterações surge a pergunta número dois: Todas as operações estavam sendo realizadas no local correto? Ficava mais fácil notar que determinadas classes estavam fazendo mais do que deveriam fazer, uma vez que o retorno de cada método era mais consciente.


No final, a refatoração obteve sucesso, todos os retornos de métodos que eram List<T> foram substituídos por uma interface adequada. Com um pouco mais de pesquisa foi possível notar que não só desenvolvedores com costume da interface List em Java faziam referência para List<T>, mas muitas pessoas utilizam a referência para List<T> devido aos recursos providos pela implementação. Um método como AddRange(IEnumerable<T>) poderia ser chamado em qualquer local. Uma proposta tentadora. Entretanto, depender de abstrações faz com que o código se torne mais flexível. E depender de interfaces que contem apenas as funcionalidades necessárias, limita a não tentar fazer em uma classe o que não deveria ser responsabilidade dela.