Classes genéricas (Guia de programação em C#)

As classes genéricas encapsulam operações que não são específicas de um determinado tipo de dados. O uso mais comum para classes genéricas é com coleções como listas vinculadas, tabelas de hash, pilhas, filas, árvores e assim por diante. Operações como adicionar e remover itens da coleção são realizadas basicamente da mesma maneira, independentemente do tipo de dados armazenados.

Para a maioria dos cenários que exigem classes de coleção, a abordagem recomendada é usar as fornecidas na biblioteca de classes .NET. Para obter mais informações sobre como usar essas classes, consulte Coleções genéricas no .NET.

Normalmente, você cria classes genéricas começando com uma classe concreta existente e alterando tipos em parâmetros de tipo, um de cada vez, até atingir o equilíbrio ideal de generalização e usabilidade. Ao criar suas próprias classes genéricas, considerações importantes incluem o seguinte:

  • Quais tipos generalizar em parâmetros de tipo.

    Como regra, quanto mais tipos você puder parametrizar, mais flexível e reutilizável seu código se torna. No entanto, demasiada generalização pode criar código difícil de ler ou compreender por outros programadores.

  • Quais restrições, se houver, devem ser aplicadas aos parâmetros de tipo (consulte Restrições nos parâmetros de tipo).

    Uma boa regra é aplicar o máximo de restrições possíveis que ainda permitirão que você manipule os tipos que você deve manipular. Por exemplo, se você souber que sua classe genérica se destina a ser usada apenas com tipos de referência, aplique a restrição de classe. Isso evitará o uso não intencional de sua classe com tipos de valor e permitirá que você use o as operador em T, e verifique se há valores nulos.

  • Se o comportamento genérico deve ser fatorado em classes e subclasses base.

    Como as classes genéricas podem servir como classes base, as mesmas considerações de design se aplicam aqui como as classes não genéricas. Consulte as regras sobre herdar de classes base genéricas mais adiante neste tópico.

  • Se deve implementar uma ou mais interfaces genéricas.

    Por exemplo, se você estiver criando uma classe que será usada para criar itens em uma coleção baseada em genéricos, talvez seja necessário implementar uma interface como IComparable<T> onde T é o tipo da sua classe.

Para obter um exemplo de uma classe genérica simples, consulte Introdução aos genéricos.

As regras para parâmetros de tipo e restrições têm várias implicações para o comportamento de classe genérica, especialmente em relação à herança e acessibilidade de membros. Antes de prosseguir, você deve entender alguns termos. Para uma classe Node<T>, genérica, o código do cliente pode fazer referência à classe especificando um argumento type - para criar um tipo construído fechado (Node<int>); ou deixando o parâmetro type não especificado - por exemplo, quando você especifica uma classe base genérica, para criar um tipo construído aberto (Node<T>). As classes genéricas podem herdar de classes de base de concreto, construídas fechadas ou abertas:

class BaseNode { }
class BaseNodeGeneric<T> { }

// concrete type
class NodeConcrete<T> : BaseNode { }

//closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { }

//open constructed type
class NodeOpen<T> : BaseNodeGeneric<T> { }

Classes não genéricas, em outras palavras, concretas, podem herdar de classes base construídas fechadas, mas não de classes construídas abertas ou de parâmetros de tipo, porque não há nenhuma maneira em tempo de execução para o código do cliente fornecer o argumento de tipo necessário para instanciar a classe base.

//No error
class Node1 : BaseNodeGeneric<int> { }

//Generates an error
//class Node2 : BaseNodeGeneric<T> {}

//Generates an error
//class Node3 : T {}

As classes genéricas que herdam de tipos construídos abertos devem fornecer argumentos de tipo para quaisquer parâmetros de tipo de classe base que não são compartilhados pela classe herdeira, conforme demonstrado no código a seguir:

class BaseNodeMultiple<T, U> { }

//No error
class Node4<T> : BaseNodeMultiple<T, int> { }

//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }

//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}

As classes genéricas que herdam de tipos construídos abertos devem especificar restrições que são um superconjunto de, ou implicam, as restrições no tipo base:

class NodeItem<T> where T : System.IComparable<T>, new() { }
class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }

Os tipos genéricos podem usar vários parâmetros de tipo e restrições, da seguinte maneira:

class SuperKeyType<K, V, U>
    where U : System.IComparable<U>
    where V : new()
{ }

Os tipos de construção aberta e fechada podem ser usados como parâmetros do método:

void Swap<T>(List<T> list1, List<T> list2)
{
    //code to swap items
}

void Swap(List<int> list1, List<int> list2)
{
    //code to swap items
}

Se uma classe genérica implementa uma interface, todas as instâncias dessa classe podem ser convertidas para essa interface.

As classes genéricas são invariantes. Em outras palavras, se um parâmetro de entrada especificar um List<BaseClass>, você obterá um erro em tempo de compilação se tentar fornecer um List<DerivedClass>arquivo .

Consulte também