Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Dica
Novo no desenvolvimento de software? Comece primeiro com os tutoriais de Introdução . Você encontrará genéricos assim que usar coleções como List<T>.
Experimentou em outro idioma? Os genéricos de C# são semelhantes aos genéricos em Java ou aos modelos no C++, mas com informações completas de tipo de runtime e sem remoção de tipo. Percorra as seções de expressões de coleção e covariância e contravariância para padrões específicos de C#.
Os genéricos permitem que você escreva código que funcione com qualquer tipo, mantendo a segurança completa de tipos. Em vez de escrever classes ou métodos separados para int, stringe todos os outros tipos de que você precisa, escreva uma versão com um ou mais parâmetros de tipo (como T, ou TKey e ) e especifique TValueos tipos reais ao usá-la. O compilador verifica os tipos em tempo de compilação; portanto, você não precisa de conversões de tipo de runtime nem corre riscos InvalidCastException.
Você encontra genéricos constantemente no C# do dia a dia. Coleções, tipos de retorno assíncronos, delegados e LINQ dependem de tipos genéricos:
List<int> scores = [95, 87, 72, 91];
Dictionary<string, decimal> prices = new()
{
["Widget"] = 19.99m,
["Gadget"] = 29.99m
};
Task<string> greeting = Task.FromResult("Hello, generics!");
Func<int, bool> isPositive = n => n > 0;
Console.WriteLine($"First score: {scores[0]}");
Console.WriteLine($"Widget price: {prices["Widget"]:C}");
Console.WriteLine($"Greeting: {await greeting}");
Console.WriteLine($"Is 5 positive? {isPositive(5)}");
Em cada caso, o argumento de tipo em colchetes angulares (<int>, <string>, <Product>) informa ao tipo genérico em que tipo de dados ele contém ou opera. O compilador impõe a segurança do tipo. Você não pode adicionar acidentalmente um string a um List<int>.
Consumindo tipos genéricos
Com mais frequência, você consume tipos genéricos da biblioteca de classes .NET em vez de criar seus próprios. As seções a seguir mostram os tipos genéricos mais comuns que você usará.
Coleções genéricas
O System.Collections.Generic namespace fornece classes de coleção seguras para tipos. Sempre use essas coleções em vez de coleções não genéricas, como ArrayList:
// A strongly typed list of strings
List<string> names = ["Alice", "Bob", "Carol"];
names.Add("Dave");
// names.Add(42); // Compile-time error: can't add an int to List<string>
// A dictionary mapping string keys to int values
var inventory = new Dictionary<string, int>
{
["Apples"] = 50,
["Oranges"] = 30
};
inventory["Bananas"] = 25;
// A set that prevents duplicates
HashSet<int> uniqueIds = [1, 2, 3, 1, 2];
Console.WriteLine($"Unique count: {uniqueIds.Count}"); // 3
// A FIFO queue
Queue<string> tasks = new();
tasks.Enqueue("Build");
tasks.Enqueue("Test");
Console.WriteLine($"Next task: {tasks.Dequeue()}"); // Build
As coleções genéricas impedem erros de tipo de runtime porque os erros surgem durante a compilação. Essas coleções também evitam o boxing para tipos de valor, o que melhora o desempenho.
Métodos genéricos
Um método genérico declara seu próprio parâmetro de tipo. O compilador geralmente infere o argumento de tipo dos valores que você passa, portanto, você não precisa especificá-lo explicitamente:
static void Print<T>(T value) =>
Console.WriteLine($"Value: {value}");
Print(42); // Compiler infers T as int
Print("hello"); // Compiler infers T as string
Print(3.14); // Compiler infers T as double
Na chamada Print(42), o compilador infere T como int a partir do argumento. Você pode escrever Print<int>(42) explicitamente, mas a inferência de tipo mantém o código mais limpo.
Expressões de coleção
As expressões de coleção (C# 12) fornecem uma sintaxe concisa para criar coleções. Use colchetes em vez de chamadas de construtor ou sintaxe de inicializador:
// Create a list with a collection expression
List<string> fruits = ["Apple", "Banana", "Cherry"];
// Create an array
int[] numbers = [1, 2, 3, 4, 5];
// Works with any supported collection type
IReadOnlyList<double> temperatures = [72.0, 68.5, 75.3];
Console.WriteLine($"Fruits: {string.Join(", ", fruits)}");
Console.WriteLine($"Numbers: {string.Join(", ", numbers)}");
Console.WriteLine($"Temps: {string.Join(", ", temperatures)}");
O operador de spread (..) inlineia os elementos de uma coleção em outra, o que é útil para combinar sequências:
List<int> first = [1, 2, 3];
List<int> second = [4, 5, 6];
// Spread both lists into a new combined list
List<int> combined = [.. first, .. second];
Console.WriteLine(string.Join(", ", combined));
// Output: 1, 2, 3, 4, 5, 6
// Add extra elements alongside spreads
List<int> withExtras = [0, .. first, 99, .. second];
Console.WriteLine(string.Join(", ", withExtras));
// Output: 0, 1, 2, 3, 99, 4, 5, 6
As expressões de coleção funcionam com matrizes, List<T>, Span<T>ImmutableArray<T>e qualquer tipo que dê suporte ao padrão do construtor de coleções. Para obter a referência de sintaxe completa, consulte Expressões de coleção.
Inicialização de dicionário
Você pode inicializar dicionários concisamente com inicializadores de indexador. Essa sintaxe usa colchetes para definir pares chave-valor:
Dictionary<string, int> scores = new()
{
["Alice"] = 95,
["Bob"] = 87,
["Carol"] = 92
};
foreach (var (name, score) in scores)
{
Console.WriteLine($"{name}: {score}");
}
Você pode mesclar dicionários copiando um e aplicando substituições:
Dictionary<string, int> defaults = new()
{
["Timeout"] = 30,
["Retries"] = 3
};
Dictionary<string, int> overrides = new()
{
["Timeout"] = 60
};
// Merge defaults and overrides into a new dictionary
Dictionary<string, int> config = new(defaults);
foreach (var (key, value) in overrides)
{
config[key] = value;
}
Console.WriteLine($"Timeout: {config["Timeout"]}"); // 60
Console.WriteLine($"Retries: {config["Retries"]}"); // 3
Restrições de tipo
Restrições restringem quais argumentos de tipo um tipo ou método genérico aceita. As restrições permitem que você chame métodos ou acesse propriedades do parâmetro de tipo que não estariam disponíveis apenas com object.
static T Max<T>(T a, T b) where T : IComparable<T> =>
a.CompareTo(b) >= 0 ? a : b;
Console.WriteLine(Max(3, 7)); // 7
Console.WriteLine(Max("apple", "banana")); // banana
static T CreateDefault<T>() where T : new() => new T();
var list = CreateDefault<List<int>>(); // Creates an empty List<int>
Console.WriteLine($"Empty list count: {list.Count}"); // 0
As restrições mais comuns são:
| Constraint | Meaning |
|---|---|
where T : class |
T deve ser um tipo de referência |
where T : struct |
T deve ser um tipo de valor não anulável |
where T : new() |
T deve ter um construtor público sem parâmetros |
where T : BaseClass |
T deve derivar de BaseClass |
where T : IInterface |
T deve implementar IInterface |
Você pode combinar restrições: where T : class, IComparable<T>, new(). Restrições menos comuns incluem where T : System.Enum, where T : System.Delegatee where T : unmanaged para cenários especializados. Para obter a lista completa, consulte Restrições nos parâmetros de tipo.
Covariância e contravariância
Covariância e contravariância descrevem como os tipos genéricos se comportam com herança. Eles determinam se você pode usar um argumento de tipo mais derivado ou menos derivado do que o especificado originalmente:
// Covariance: IEnumerable<Dog> can be used as IEnumerable<Animal>
// because IEnumerable<out T> is covariant
List<Dog> dogs = [new("Rex"), new("Buddy")];
IEnumerable<Animal> animals = dogs; // Allowed because Dog derives from Animal
foreach (var animal in animals)
{
Console.WriteLine(animal.Name);
}
// Contravariance: Action<Animal> can be used as Action<Dog>
// because Action<in T> is contravariant
Action<Animal> printAnimal = a => Console.WriteLine($"Animal: {a.Name}");
Action<Dog> printDog = printAnimal; // Allowed because any Animal handler can handle Dog
printDog(new Dog("Spot"));
-
Covariância (
out T): umIEnumerable<Dog>pode ser usado ondeIEnumerable<Animal>é esperado porqueDogderiva deAnimal. Aoutpalavra-chave no parâmetro de tipo permite isso. Parâmetros de tipo covariante só podem aparecer em posições de saída (tipos de retorno). -
Contravariância (
in T): umAction<Animal>pode ser usado ondeAction<Dog>é esperado porque qualquer ação que manipulaAnimaltambém pode manipularDog. Ainpalavra-chave permite isso. Parâmetros de tipo contravariante só podem aparecer em posições de entrada (parâmetros).
Muitas interfaces integradas e delegates já são variantes: IEnumerable<out T>, IReadOnlyList<out T>, Func<out TResult> e Action<in T>. Você se beneficia da variação automaticamente quando trabalha com esses tipos. Para obter um tratamento aprofundado da criação de interfaces e delegates de variante, consulte Covariância e contravariância.
Criar seus próprios tipos genéricos
Você pode definir suas próprias classes genéricas, structs, interfaces e métodos. O exemplo a seguir mostra uma lista vinculada genérica simples para ilustração. Na prática, use List<T> ou outra coleção embutida:
public class GenericList<T>
{
private class Node(T data)
{
public T Data { get; set; } = data;
public Node? Next { get; set; }
}
private Node? head;
public void AddHead(T data)
{
var node = new Node(data) { Next = head };
head = node;
}
public IEnumerator<T> GetEnumerator()
{
var current = head;
while (current is not null)
{
yield return current.Data;
current = current.Next;
}
}
}
var list = new GenericList<int>();
for (var i = 0; i < 5; i++)
{
list.AddHead(i);
}
foreach (var item in list)
{
Console.Write($"{item} ");
}
Console.WriteLine();
// Output: 4 3 2 1 0
Tipos genéricos não se limitam a classes. Você pode definir tipos interface, struct e record genéricos. Para obter mais informações sobre como criar algoritmos genéricos e combinações de restrições complexas, consulte Generics em .NET.