C#: OO
Introdução
A Programação Orientada a Objetos (POO) é um paradigma de programação que utiliza "objetos" para modelar dados e funcionalidades. O C# é uma linguagem que suporta plenamente a POO, oferecendo recursos poderosos para criar aplicações robustas, escaláveis e de fácil manutenção.
Este guia atualizado abrange os conceitos fundamentais da POO em C#, bem como os recursos mais recentes da linguagem que aprimoram a programação orientada a objetos. Exploraremos desde os princípios básicos até as práticas avançadas, incluindo exemplos práticos para consolidar o entendimento.
1. Princípios Fundamentais da POO
1.1. Abstração
Abstração é o processo de ocultar detalhes complexos e expor apenas os essenciais. Em C#, isso é alcançado através de classes, interfaces e métodos abstratos.
Exemplo:
// Abstração através de uma classe abstrata
public abstract class Forma
{
public abstract double CalcularArea();
}
1.## 2. Encapsulamento
Encapsulamento envolve esconder os dados internos de um objeto e expor apenas o necessário através de métodos públicos. Isso protege o estado interno do objeto de alterações externas indevidas.
Exemplo:
public class ContaBancaria
{
private decimal saldo;
public decimal Saldo
{
get { return saldo; }
private set
{
if (value >= 0)
saldo = value;
}
}
public void Depositar(decimal valor)
{
if (valor > 0)
Saldo += valor;
}
public void Sacar(decimal valor)
{
if (valor > 0 && Saldo >= valor)
Saldo -= valor;
}
}
1.3. Herança
Herança permite que uma classe (derivada) herde propriedades e métodos de outra classe (base). Isso promove a reutilização de código e a criação de hierarquias lógicas.
Exemplo:
public class Animal
{
public void Respirar()
{
Console.WriteLine("Respirando...");
}
}
public class Cachorro : Animal
{
public void Latir()
{
Console.WriteLine("Latindo...");
}
}
// Uso
Cachorro cachorro = new Cachorro();
cachorro.Respirar(); // Herança da classe Animal
cachorro.Latir();
1.4. Polimorfismo
Polimorfismo permite que objetos de diferentes classes sejam tratados de forma uniforme através de uma interface comum. Existem dois tipos principais: polimorfismo de tempo de compilação (sobrecarga) e de tempo de execução (substituição).
Exemplo de Polimorfismo de Substituição:
public abstract class Forma
{
public abstract double CalcularArea();
}
public class Circulo : Forma
{
public double Raio { get; set; }
public override double CalcularArea()
{
return Math.PI * Raio * Raio;
}
}
public class Retangulo : Forma
{
public double Largura { get; set; }
public double Altura { get; set; }
public override double CalcularArea()
{
return Largura * Altura;
}
}
// Uso
List<Forma> formas = new List<Forma>
{
new Circulo { Raio = 5 },
new Retangulo { Largura = 4, Altura = 6 }
};
foreach (var forma in formas)
{
Console.WriteLine($"Área: {forma.CalcularArea()}");
}
2. Conceitos Avançados da POO em C#
2.1. Classes e Objetos
Em C#, classes são moldes para criar objetos. Elas contêm campos, propriedades, métodos e eventos.
Exemplo:
public class Carro
{
// Propriedades
public string Modelo { get; set; }
public string Cor { get; set; }
public int Ano { get; set; }
// Método
public void Acelerar()
{
Console.WriteLine($"{Modelo} está acelerando.");
}
}
// Criação de objeto
Carro meuCarro = new Carro
{
Modelo = "Toyota Corolla",
Cor = "Prata",
Ano = 2020
};
meuCarro.Acelerar();
2.2. Interfaces
Interfaces definem contratos que as classes podem implementar. Elas contêm apenas a assinatura dos métodos, propriedades, eventos ou indexadores.
Exemplo:
public interface IRepositorio<T>
{
void Adicionar(T item);
void Remover(T item);
T ObterPorId(int id);
IEnumerable<T> ObterTodos();
}
public class RepositorioEmMemoria<T> : IRepositorio<T>
{
private List<T> itens = new List<T>();
public void Adicionar(T item) => itens.Add(item);
public void Remover(T item) => itens.Remove(item);
public T ObterPorId(int id)
{
// Implementação simplificada
return itens.ElementAtOrDefault(id);
}
public IEnumerable<T> ObterTodos() => itens;
}
2.3. Classes Abstratas
Classes Abstratas não podem ser instanciadas diretamente e podem conter métodos abstratos que devem ser implementados pelas classes derivadas.
Exemplo:
public abstract class Veiculo
{
public string Marca { get; set; }
public abstract void Mover();
}
public class Bicicleta : Veiculo
{
public override void Mover()
{
Console.WriteLine("Pedalando a bicicleta.");
}
}
public class Carro : Veiculo
{
public override void Mover()
{
Console.WriteLine("Dirigindo o carro.");
}
}
// Uso
List<Veiculo> veiculos = new List<Veiculo>
{
new Bicicleta { Marca = "Caloi" },
new Carro { Marca = "Ford" }
};
foreach (var veiculo in veiculos)
{
Console.Write($"{veiculo.Marca} está ");
veiculo.Mover();
}
C# não suporta herança múltipla de classes, mas permite que uma classe implemente múltiplas interfaces, proporcionando flexibilidade.
Exemplo:
public interface IAquatico
{
void Nadar();
}
public interface IAnimal
{
void Comer();
}
public class Golfinho : IAquatico, IAnimal
{
public void Nadar()
{
Console.WriteLine("Golfinho está nadando.");
}
public void Comer()
{
Console.WriteLine("Golfinho está comendo.");
}
}
// Uso
Golfinho golfinho = new Golfinho();
golfinho.Nadar();
golfinho.Comer();
2.5. Sobrecarga e Substituição de Métodos
Sobrecarga permite que uma classe tenha múltiplos métodos com o mesmo nome, mas com diferentes parâmetros. Substituição permite que uma classe derivada forneça uma implementação específica para um método virtual ou abstrato da classe base.
Exemplo de Sobrecarga:
public class Calculadora
{
public int Somar(int a, int b) => a + b;
public double Somar(double a, double b) => a + b;
public int Somar(int a, int b, int c) => a + b + c;
}
// Uso
Calculadora calc = new Calculadora();
Console.WriteLine(calc.Somar(2, 3)); // Saída: 5
Console.WriteLine(calc.Somar(## 2.5, 3.5)); // Saída: 6.0
Console.WriteLine(calc.Somar(1, 2, 3)); // Saída: 6
Exemplo de Substituição:
public class Animal
{
public virtual void EmitirSom()
{
Console.WriteLine("Som genérico de animal.");
}
}
public class Gato : Animal
{
public override void EmitirSom()
{
Console.WriteLine("Miau!");
}
}
public class Cachorro : Animal
{
public override void EmitirSom()
{
Console.WriteLine("Au Au!");
}
}
// Uso
List<Animal> animais = new List<Animal>
{
new Gato(),
new Cachorro()
};
foreach (var animal in animais)
{
animal.EmitirSom();
}
3. Recursos Modernos do C# para POO
3.1. Record Types
Introduzidos no C# 9, Records são tipos de referência imutáveis que facilitam a criação de objetos de valor. Eles fornecem uma implementação automática de métodos como Equals, GetHashCode e ToString.
Exemplo:
public record Pessoa(string Nome, int Idade);
// Uso
Pessoa pessoa1 = new Pessoa("Ana", 28);
Pessoa pessoa2 = new Pessoa("Ana", 28);
Console.WriteLine(pessoa1); // Saída: Pessoa { Nome = Ana, Idade = 28 }
Console.WriteLine(pessoa1 == pessoa2); // Saída: True
Benefícios:
• Imutabilidade: Facilita a criação de objetos que não podem ser alterados após a criação.
• Concorrência: Reduz problemas relacionados a estados compartilhados.
• Simplificação: Menos código para definir propriedades e métodos padrão.
3.2. Init-Only Properties
Introduzidos no C# 9, Init-Only Properties permitem que as propriedades sejam definidas apenas durante a inicialização do objeto, promovendo a imutabilidade parcial.
Exemplo:
public class Produto
{
public int Id { get; init; }
public string Nome { get; init; }
public decimal Preco { get; init; }
}
// Uso
Produto produto = new Produto
{
Id = 1,
Nome = "Notebook",
Preco = 3500.00m
};
// Tentativa de modificação após a inicialização (compilação falha)
// produto.Preco = 3000.00m;
Benefícios:
• Imutabilidade Controlada: Permite modificações apenas no momento da criação.
• Segurança: Evita alterações acidentais no estado do objeto.
3.3. Pattern Matching
Pattern Matching aprimorou a forma de verificar e extrair dados de objetos. Combinado com switch expressions e patterns, torna o código mais conciso e expressivo.
Exemplo:
public void DescreverObjeto(object obj)
{
switch (obj)
{
case int i when i > 0:
Console.WriteLine("Número inteiro positivo.");
break;
case string s:
Console.WriteLine($"Texto: {s}");
break;
case Pessoa p:
Console.WriteLine($"Pessoa: {p.Nome}, {p.Idade} anos.");
break;
default:
Console.WriteLine("Tipo desconhecido.");
break;
}
}
// Uso
DescreverObjeto(10); // Saída: Número inteiro positivo.
DescreverObjeto("Olá"); // Saída: Texto: Olá
DescreverObjeto(new Pessoa("Carlos", 35)); // Saída: Pessoa: Carlos, 35 anos.
DescreverObjeto(3.14); // Saída: Tipo desconhecido.
Benefícios:
• Legibilidade: Menos código boilerplate para verificações de tipo.
• Flexibilidade: Permite condições adicionais (guards) para correspondência de padrões.
3.4. Static Abstract Members in Interfaces
Introduzidos no C# 10, Static Abstract Members in Interfaces permitem que interfaces definam membros estáticos abstratos que devem ser implementados pelas classes que as implementam. Isso é particularmente útil para Generic Math e operações estáticas.
Exemplo:
public interface IOperacao<T>
{
static abstract T Adicionar(T a, T b);
}
public class OperacaoInt : IOperacao<int>
{
public static int Adicionar(int a, int b) => a + b;
}
public class OperacaoDouble : IOperacao<double>
{
public static double Adicionar(double a, double b) => a + b;
}
public class Calculadora
{
public T Somar<T>(T a, T b) where T : IOperacao<T>
{
return T.Adicionar(a, b);
}
}
// Uso
Calculadora calc = new Calculadora();
int somaInt = calc.Somar(5, 10); // Resultado: 15
double somaDouble = calc.Somar(5.5, 10.2); // Resultado: 15.7
Console.WriteLine($"Soma Int: {somaInt}");
Console.WriteLine($"Soma Double: {somaDouble}");
Benefícios:
• Genéricos Mais Poderosos: Permite operações estáticas em tipos genéricos.
• Flexibilidade: Facilita a implementação de métodos estáticos específicos para cada tipo.
3.5. Default Interface Methods
Também introduzidos no C# 8, Default Interface Methods permitem que interfaces contenham implementações padrão para seus métodos. Isso facilita a evolução de interfaces sem quebrar implementações existentes.
Exemplo:
public interface ILogger
{
void Log(string mensagem);
// Método com implementação padrão
void LogErro(string mensagem)
{
Log($"ERRO: {mensagem}");
}
}
public class ConsoleLogger : ILogger
{
public void Log(string mensagem)
{
Console.WriteLine(mensagem);
}
}
// Uso
ILogger logger = new ConsoleLogger();
logger.Log("Aplicação iniciada.");
logger.LogErro("Falha ao conectar ao banco de dados.");
Benefícios:
• Evolução de Interfaces: Adiciona novos métodos sem impactar implementações existentes.
• Reutilização de Código: Fornece implementações padrão que podem ser reutilizadas ou sobrescritas.
4.1. Record Types vs. Classes Tradicionais
Antes (C# 8 e anteriores):
Para criar objetos de valor imutáveis, era necessário implementar manualmente métodos como Equals, GetHashCode e definir propriedades somente leitura.
public class Pessoa { public string Nome { get; } public int Idade { get; }
public Pessoa(string nome, int idade)
{
Nome = nome;
Idade = idade;
}
public override bool Equals(object obj)
{
if (obj is Pessoa outra)
return Nome == outra.Nome && Idade == outra.Idade;
return false;
}
public override int GetHashCode() => HashCode.Combine(Nome, Idade);
public override string ToString() => $"Pessoa {{ Nome = {Nome}, Idade = {Idade} }}";
}
Depois (C# 9+):
Com Record Types, tudo isso é automatizado.
public record Pessoa(string Nome, int Idade);
// Uso Pessoa pessoa1 = new Pessoa("Ana", 28); Pessoa pessoa2 = new Pessoa("Ana", 28);
Console.WriteLine(pessoa1); // Saída: Pessoa Console.WriteLine(pessoa1 == pessoa2); // Saída: True
Benefícios:
• Menos Código Boilerplate: Reduz significativamente a quantidade de código necessário.
• Imutabilidade: Por padrão, os records são imutáveis, promovendo melhores práticas de programação.
4.2 Init-Only Properties vs. Propriedades Tradicionais
Antes (C# 8 e anteriores):
Para criar objetos parcialmente imutáveis, era comum definir setters privados ou usar construtores.
public class Produto { public int Id { get; private set; } public string Nome { get; private set; } public decimal Preco { get; private set; }
public Produto(int id, string nome, decimal preco)
{
Id = id;
Nome = nome;
Preco = preco;
}
}
Depois (C# 9+):
Com Init-Only Properties, a imutabilidade pode ser facilmente aplicada durante a inicialização.
public class Produto
{
public int Id { get; init; }
public string Nome { get; init; }
public decimal Preco { get; init; }
}
// Uso
Produto produto = new Produto
{
Id = 1,
Nome = "Notebook",
Preco = 3500.00m
};
// Tentativa de modificação após a inicialização (compilação falha)
// produto.Preco = 3000.00m;
Benefícios:
• Facilidade de Uso: Simplifica a criação de objetos imutáveis.
• Flexibilidade: Permite a construção de objetos com propriedades definidas em diferentes locais do código.
6.3. Static Abstract Members in Interfaces vs. Métodos Estáticos Tradicionais
Antes (C# 10 e anteriores):
Para operações estáticas em interfaces, era necessário contornar a falta de suporte com classes auxiliares ou métodos de extensão.
public interface IOperacao
{
// Não suportado: static T Somar(T a, T b);
}
public static class OperacaoExtensions
{
public static int Somar(this IOperacao op, int a, int b) => a + b;
}
Depois (C# 10+):
Com Static Abstract Members in Interfaces, é possível definir métodos estáticos abstratos diretamente nas interfaces.
public interface IOperacao<T>
{
static abstract T Somar(T a, T b);
}
public class OperacaoInt : IOperacao<int>
{
public static int Somar(int a, int b) => a + b;
}
public class Calculadora
{
public T Adicionar<T>(T a, T b) where T : IOperacao<T>
{
return T.Somar(a, b);
}
}
// Uso
Calculadora calc = new Calculadora();
int resultado = calc.Adicionar<int>(5, 10);
Console.WriteLine($"Soma: {resultado}"); // Saída: Soma: 15
Benefícios:
• Type Safety: Garante que as classes implementem os métodos estáticos conforme o contrato.
• Generics Mais Poderosos: Permite operações estáticas em tipos genéricos de maneira segura.
Conclusão
A Programação Orientada a Objetos em C# é amplamente suportada e continuamente aprimorada com recursos modernos que facilitam a criação de aplicações robustas e de fácil manutenção. Ao dominar os princípios fundamentais da POO e aproveitar os recursos mais recentes da linguagem, desenvolvedores podem escrever código mais limpo, eficiente e expressivo.
Este guia apresentou uma visão abrangente dos conceitos básicos e avançados da POO em C#, destacando como os recursos recentes da linguagem complementam e aprimoram a programação orientada a objetos. A aplicação consciente de princípios como SOLID e padrões de design garante que o código seja sustentável e escalável, atendendo às demandas crescentes do desenvolvimento de software moderno.
Dicas Finais:
• Pratique Regularmente: A melhor forma de internalizar os conceitos de POO é aplicá-los em projetos reais ou exercícios práticos.
• Mantenha-se Atualizado: C# continua a evoluir, portanto, acompanhe as novidades e atualizações da linguagem.
• Refatore o Código: Revise e melhore continuamente o código existente para aderir às melhores práticas de POO.
• Participe da Comunidade: Engaje-se com outros desenvolvedores para trocar experiências e aprender novas abordagens.
Last modified: 01 March 2025