Compartilhar via


Visão geral de tipos genéricos

Os desenvolvedores usam genéricos o tempo todo no .NET, seja implicitamente ou explicitamente. Ao usar o LINQ no .NET, você já notou que está trabalhando com IEnumerable<T>? Ou se você já viu uma amostra online de um "repositório genérico" para conversar com bancos de dados usando o Entity Framework, você 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á dentro.

Introduzidos pela primeira vez no .NET Framework 2.0, os genéricos são essencialmente um "modelo de código" que permite que os desenvolvedores definam estruturas de dados com segurança de tipo sem se comprometerem 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 Object. O mesmo ocorreria ao ler elementos da lista. Esse processo é conhecido como conversão boxing 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 algum 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 List<int> e adicionar somente Pessoas a List<Person>.

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

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

  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 uma saída semelhante à seguinte:

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 observar aqui é que classificar a lista genérica é significativamente mais rápido do que classificar a lista não genérica. Você também pode observar que o tipo da lista genérica é distinto ([System.Int32]), enquanto o tipo da lista não genérica é generalizado. Como o runtime sabe que o genérico List<int> é do tipo Int32, ele pode armazenar os elementos de lista em uma matriz de inteiros subjacente na memória, enquanto o não genérico ArrayList precisa converter cada elemento de lista em um objeto. Como este exemplo mostra, as conversões extras levam tempo e reduzem a velocidade da classificação da lista.

Uma vantagem adicional de o runtime saber o tipo de 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 genéricos, você não teria ideia de que tipo cada elemento era.

Consulte também