Visão geral dos tipos genéricos

Os desenvolvedores usam genéricos o tempo todo no .NET, seja implícita ou explicitamente. Quando você usa o LINQ no .NET, você já notou que está trabalhando com IEnumerable<T>? Ou se você já viu uma amostra on-line de um "repositório genérico" para falar com bancos de dados usando o Entity Framework, viu que a maioria dos métodos retorna?IQueryable<T> Você deve ter se perguntado o que é o T nesses exemplos e por que ele está lá.

Introduzidos pela primeira vez no .NET Framework 2.0, os genéricos são essencialmente um "modelo de código" que permite aos desenvolvedores definir estruturas de dados seguras sem se comprometer com um tipo de dados real. Por exemplo, List<T> é uma coleção genérica que pode ser declarada e usada com qualquer tipo, como List<int>, List<string>ou List<Person>.

Para entender por que os genéricos são úteis, vamos dar uma olhada em uma classe específica antes e depois de adicionar genéricos: ArrayList. No .NET Framework 1.0, os ArrayList elementos eram do tipo Object. Qualquer elemento adicionado à coleção foi silenciosamente convertido em um Objectarquivo . O mesmo aconteceria ao ler elementos da lista. Esse processo é conhecido como boxe e unboxing, e afeta o desempenho. Além do desempenho, no entanto, não há como determinar o tipo de dados na lista em tempo de compilação, o que torna o código frágil. Os genéricos resolvem esse problema definindo o tipo de dados que cada instância da lista conterá. Por exemplo, você só pode adicionar inteiros e List<int> adicionar somente Pessoas ao List<Person>.

Os genéricos também estão disponíveis em tempo de execução. O tempo de execução sabe que tipo de estrutura de dados você está usando e pode armazená-la na memória de forma mais eficiente.

O exemplo a seguir é um pequeno programa que ilustra a eficiência de conhecer o tipo de estrutura de dados em tempo de execução:

  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Diagnostics;

  namespace GenericsExample {
    class Program {
      static void Main(string[] args) {
        //generic list
        List<int> ListGeneric = new List<int> { 5, 9, 1, 4 };
        //non-generic list
        ArrayList ListNonGeneric = new ArrayList { 5, 9, 1, 4 };
        // timer for generic list sort
        Stopwatch s = Stopwatch.StartNew();
        ListGeneric.Sort();
        s.Stop();
        Console.WriteLine($"Generic Sort: {ListGeneric}  \n Time taken: {s.Elapsed.TotalMilliseconds}ms");

        //timer for non-generic list sort
        Stopwatch s2 = Stopwatch.StartNew();
        ListNonGeneric.Sort();
        s2.Stop();
        Console.WriteLine($"Non-Generic Sort: {ListNonGeneric}  \n Time taken: {s2.Elapsed.TotalMilliseconds}ms");
        Console.ReadLine();
      }
    }
  }

Este programa produz resultados semelhantes aos seguintes:

Generic Sort: System.Collections.Generic.List`1[System.Int32]
 Time taken: 0.0034ms
Non-Generic Sort: System.Collections.ArrayList
 Time taken: 0.2592ms

A primeira coisa que você pode notar aqui é que classificar a lista genérica é significativamente mais rápido do que classificar a lista não genérica. Você também pode notar que o tipo para a lista genérica é distinto ([System.Int32]), enquanto o tipo para a lista não genérica é generalizado. Como o tempo de execução sabe que o genérico List<int> é do tipo Int32, ele pode armazenar os elementos da lista em uma matriz inteira subjacente na memória, enquanto o não genérico ArrayList tem que converter cada elemento da lista em um objeto. Como mostra este exemplo, as transmissões extras ocupam tempo e diminuem a classificação da lista.

Uma vantagem adicional do tempo de execução saber o tipo do seu genérico é uma melhor experiência de depuração. Ao depurar um genérico em C#, você sabe que tipo cada elemento está em sua estrutura de dados. Sem os genéricos, você não teria ideia de que tipo cada elemento era.

Consulte também