Dominando os genéricos em C#
Os genéricos em C# tornaram-se parte integrante da linguagem desde a sua introdução, proporcionando inúmeros benefícios, mesmo que o seu funcionamento não seja totalmente compreendido por todos os desenvolvedores. Em seu vídeo " Como criar genéricos em C#, incluindo novos recursos ", Tim Corey explica por que os genéricos são importantes, como criá-los e demonstra suas aplicações práticas.
Este artigo fornece um guia completo sobre genéricos em C#, oferecendo informações valiosas a partir do vídeo de Tim Corey sobre o assunto. Abrange os fundamentos dos genéricos, incluindo segurança de tipo, benefícios de desempenho e aplicações práticas. O artigo também explora a criação de métodos genéricos, classes, interfaces e a aplicação de restrições a genéricos. Além disso, destaca casos de uso reais e a importância de usar genéricos para escrever código eficiente e com segurança de tipos.
Introdução
Os genéricos em C# oferecem uma maneira poderosa de criar código flexível, reutilizável e com segurança de tipos, permitindo que os desenvolvedores definam classes, métodos, interfaces e coleções que funcionam com qualquer tipo de dado. Ao usar uma classe genérica ou um método genérico, os desenvolvedores podem definir um parâmetro de tipo genérico (por exemplo, T) que pode representar qualquer tipo de dado. Isso elimina a necessidade de duplicação de código e melhora a reutilização de código, garantindo a segurança de tipos em tempo de compilação. Classes de coleção genéricas como List e Dictionary<TKey, TValue> permitem o manuseio eficiente de diferentes tipos de dados, enquanto interfaces genéricas e delegados genéricos possibilitam a criação de tipos genéricos personalizados que podem trabalhar com múltiplos parâmetros de tipo. Ao aproveitar os recursos genéricos, os desenvolvedores podem maximizar a eficiência do código e minimizar as desvantagens das classes não genéricas. Essa flexibilidade permite a criação de mais código reutilizável sem sacrificar o desempenho ou a segurança de tipos.
Tim em (0:00) introduz o tópico enfatizando o uso generalizado de genéricos em C#. Seu objetivo é explicar por que os genéricos são essenciais e demonstrar como criá-los e utilizá-los de forma eficaz.
Criando o Projeto
Tim começa criando um novo aplicativo de console chamado "GenericsDemoApp" para se concentrar exclusivamente na demonstração de genéricos sem distrações da interface do usuário. Ele configurou o projeto usando .NET 8 e Visual Studio 2022.
Noções básicas de genéricos
Tim, às (2:22), começa com um exemplo usando a classe List para ilustrar o conceito de genéricos. Os genéricos permitem especificar o tipo de elementos que uma coleção pode conter, proporcionando segurança de tipos e evitando erros em tempo de execução.
List<int> numbers = new List<int> { 1, 2, 3 };
List<string> strings = new List<string> { "Tim", "Corey", "Sue" };
List<int> numbers = new List<int> { 1, 2, 3 };
List<string> strings = new List<string> { "Tim", "Corey", "Sue" };
A List garante que apenas inteiros possam ser adicionados à lista de números e apenas strings possam ser adicionadas à lista de strings.
Segurança e eficiência do tipo
Tim destaca a importância da segurança de tipo proporcionada pelos medicamentos genéricos. O compilador verifica os tipos em tempo de projeto, evitando incompatibilidades de tipos e garantindo a execução segura do código. Os genéricos também evitam a necessidade de encapsulamento e desempacotamento, resultando em um código mais eficiente.
Ineficiência de Coleções Não Genéricas
Para demonstrar a ineficiência do uso de coleções não genéricas, Tim cria uma Lista e mostra como ela pode armazenar diferentes tipos de objetos.
List<object> objects = new List<object> { "Tim", 4, 3.6m };
List<object> objects = new List<object> { "Tim", 4, 3.6m };
Ele explica que o uso de coleções não genéricas pode levar a incompatibilidades de tipos e ineficiências devido ao boxing e unboxing.
Comparação de desempenho
Tim, às 6:15, realiza uma comparação de desempenho entre o uso de uma Lista.
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 1; i <= 1_000_000; i++)
{
objects.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of object elapsed time: {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Restart();
for (int i = 1; i <= 1_000_000; i++)
{
numbers.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of int elapsed time: {stopwatch.ElapsedMilliseconds} ms");
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 1; i <= 1_000_000; i++)
{
objects.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of object elapsed time: {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Restart();
for (int i = 1; i <= 1_000_000; i++)
{
numbers.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of int elapsed time: {stopwatch.ElapsedMilliseconds} ms");
Os resultados mostram que adicionar números inteiros a uma lista

Criando um método de verificação de tipo
Tim em (10:14) demonstra como criar um método genérico chamado TypeChecker. Este método verifica o tipo de um determinado valor e o imprime, ilustrando a flexibilidade e o poder dos genéricos.
public static void TypeChecker<t>(T value)
{
Console.WriteLine($"Type: {typeof(T)}, Value: {value}");
}
public static void TypeChecker<t>(T value)
{
Console.WriteLine($"Type: {typeof(T)}, Value: {value}");
}
O método TypeChecker usa o operador typeof para determinar o tipo do parâmetro genérico T e imprime tanto o tipo quanto o valor.
Utilizando o método de verificação de tipo
Tim fornece exemplos de como chamar o método TypeChecker com diferentes tipos de argumentos.
TypeChecker(1); // Type: System.Int32, Value: 1
TypeChecker("Tim"); // Type: System.String, Value: Tim
TypeChecker(1.1); // Type: System.Double, Value: 1.1
TypeChecker(1); // Type: System.Int32, Value: 1
TypeChecker("Tim"); // Type: System.String, Value: Tim
TypeChecker(1.1); // Type: System.Double, Value: 1.1
Ao passar vários tipos para o método TypeChecker, Tim mostra como os genéricos podem lidar com diferentes tipos de dados de forma integrada.
Criando uma Classe Genérica: Lista Melhorada
Tim, em (16:25), passa a criar uma classe genérica chamada BetterList. Essa classe encapsula uma lista de um tipo especificado e fornece funcionalidades adicionais.
public class BetterList<t>
{
private List<t> data = new List<t>();
public void AddToList(T value)
{
data.Add(value);
Console.WriteLine($"{value} has been added to the list");
}
}
public class BetterList<t>
{
private List<t> data = new List<t>();
public void AddToList(T value)
{
data.Add(value);
Console.WriteLine($"{value} has been added to the list");
}
}
A classe BetterList inclui uma List privada e um método AddToList que adiciona um valor à lista e imprime uma mensagem indicando a adição.
Usando a classe Better List
Tim fornece exemplos de como usar a classe BetterList com diferentes tipos.
BetterList<int> betterNumbers = new BetterList<int>();
betterNumbers.AddToList(5);
BetterList<PersonRecord> people = new BetterList<PersonRecord>();
people.AddToList(new PersonRecord("Tim", "Corey"));
BetterList<int> betterNumbers = new BetterList<int>();
betterNumbers.AddToList(5);
BetterList<PersonRecord> people = new BetterList<PersonRecord>();
people.AddToList(new PersonRecord("Tim", "Corey"));
Nestes exemplos, BetterList
Criando uma interface genérica
Tim em (21:48) introduz a ideia de uma interface genérica chamada IImportance. Esta interface define um método para determinar qual de dois valores é mais importante.
public interface IImportance<t>
{
T MostImportant(T a, T b);
}
public interface IImportance<t>
{
T MostImportant(T a, T b);
}
Implementando a Interface Genérica
Tim mostra como implementar essa interface para diferentes tipos. Ele começa com uma implementação para números inteiros.
public class EvaluateImportance : IImportance<int>
{
public int MostImportant(int a, int b)
{
return a > b ? a : b;
}
}
public class EvaluateImportance : IImportance<int>
{
public int MostImportant(int a, int b)
{
return a > b ? a : b;
}
}
Em seguida, ele implementa a interface para strings, usando o comprimento das strings para determinar a importância.
public class EvaluateStringImportance : IImportance<string>
{
public string MostImportant(string a, string b)
{
return a.Length > b.Length ? a : b;
}
}
public class EvaluateStringImportance : IImportance<string>
{
public string MostImportant(string a, string b)
{
return a.Length > b.Length ? a : b;
}
}
Essas implementações demonstram como a mesma interface pode ser aplicada a diferentes tipos com lógica específica para cada tipo.
Aplicando restrições a genéricos
Tim em (25:21) explica como aplicar restrições aos genéricos, garantindo que eles atendam a certas condições. Por exemplo, um tipo genérico pode ser restringido a ter um construtor vazio ou a implementar uma interface específica.
public class SampleClass<t> where T : new()
{
// Class implementation
}
public class SampleClassWithInterface<t> where T : IImportance<t>
{
// Class implementation
}
public class SampleClass<t> where T : new()
{
// Class implementation
}
public class SampleClassWithInterface<t> where T : IImportance<t>
{
// Class implementation
}
Essas restrições ajudam a garantir que o tipo genérico atenda aos critérios necessários, evitando erros em tempo de execução e aprimorando a segurança de tipos.
Implementação do INumber pela Microsoft
Tim discute como a Microsoft usa a interface INumber para restringir operações numéricas. Isso permite operações aritméticas como adição e subtração em tipos genéricos.
public class MathOperations<t> where T : INumber<t>
{
public T Add(T x, T y)
{
return x + y;
}
}
public class MathOperations<t> where T : INumber<t>
{
public T Add(T x, T y)
{
return x + y;
}
}
Ao restringir o tipo genérico T para INumber, garante-se que o tipo suporte operações numéricas.
Utilizando genéricos com diferentes tipos numéricos
Tim em (33:55) expande a classe MathOperations para demonstrar como os genéricos podem ser usados com diferentes tipos numéricos, como doubles e decimais.
Tim mostra como criar instâncias de MathOperations para números inteiros e números de ponto flutuante de dupla precisão:
MathOperations<int> intMath = new MathOperations<int>();
Console.WriteLine(intMath.Add(1, 4)); // Outputs: 5
MathOperations<double> doubleMath = new MathOperations<double>();
Console.WriteLine(doubleMath.Add(1.5, 4.3)); // Outputs: 5.8
MathOperations<int> intMath = new MathOperations<int>();
Console.WriteLine(intMath.Add(1, 4)); // Outputs: 5
MathOperations<double> doubleMath = new MathOperations<double>();
Console.WriteLine(doubleMath.Add(1.5, 4.3)); // Outputs: 5.8
Isso demonstra a flexibilidade dos genéricos, permitindo que diferentes tipos numéricos sejam tratados perfeitamente dentro da mesma classe.
Lidando com diferentes tipos numéricos
Tim enfatiza a importância da segurança de tipos mostrando que não é possível misturar diferentes tipos numéricos. Por exemplo, tentar somar um número de ponto flutuante de dupla precisão (double) a um número inteiro resultará em um erro de compilação.
// This will result in a compile-time error
// Console.WriteLine(intMath.Add(1.5, 4));
// This will result in a compile-time error
// Console.WriteLine(intMath.Add(1.5, 4));
Evitando a sobrecarga de conversão de tipos
Tim explica os benefícios de usar genéricos para evitar a sobrecarga associada à conversão de tipos. Por exemplo, converter números inteiros em números de ponto flutuante de dupla precisão para operações matemáticas e depois convertê-los novamente em números inteiros pode ser custoso. O uso de genéricos permite operações diretas nos tipos nativos, preservando o desempenho e a precisão.
Genéricos na prática
Tim recomenda cautela ao usar genéricos, sugerindo que os desenvolvedores os utilizem adequadamente e evitem o uso excessivo. Ele destaca os benefícios dos genéricos, como segurança de tipos, redução de boxing e unboxing, verificação em tempo de compilação e maior legibilidade do código.
Ele também aponta que genéricos são comuns em coleções como List e Dictionary<TKey, TValue>, bem como em frameworks de logging que podem lidar com vários tipos sem precisar conhecer detalhes específicos antecipadamente.
Conclusão
A análise detalhada de Tim Corey sobre genéricos avançados em C# oferece informações valiosas sobre suas aplicações práticas e benefícios. Se você deseja aprofundar seu conhecimento sobre genéricos e ver exemplos práticos em ação, não deixe de assistir ao vídeo detalhado de Tim Corey sobre genéricos em C# . Suas explicações claras e demonstrações práticas ajudarão você a compreender totalmente os conceitos e aplicá-los com eficácia em seus próprios projetos.
