C#: Modificadores de acesso e propriedades

1. Modificadores de acesso

Os modificadores de acesso controlam a acessibilidade dos membros da classe e constituem o mecanismo central do encapsulamento. O C# oferece seis níveis de acesso:

Modificador Mesma classe Subclasse (mesma assembly) Subclasse (assembly diferente) Mesma assembly Assembly diferente
public
private
protected
internal
protected internal
private protected
💡 protected internal é uma relação “OU”: subclasses ou que estejam no mesmo assembly podem acessar. private protected é uma relação “E”: deve ser tanto uma subclasse quanto estar no mesmo assembly.

▶ Exemplo

CSHARP
public class Animal
{
    public string Species;
    private int health = 100;
    protected int energy = 90;
    internal string category = "Mammal";

    public void ShowInfo()
    {
        Console.WriteLine($"Species: {Species}, Health: {health}, Energy: {energy}, Category: {category}");
    }

    public void SetHealth(int h) { health = h; }
}

public class Dog : Animal
{
    public void Run()
    {
        energy -= 10;
        category = "Mammal";
    }
}

class Program
{
    static void Main()
    {
        Animal a = new Dog { Species = "Dog" };
        a.ShowInfo();
        ((Dog)a).Run();
        a.ShowInfo();
    }
}
▶ Experimente
TEXT 📖 Somente leitura
Species: Dog, Health: 100, Energy: 90, Category: Mammal
Species: Dog, Health: 100, Energy: 80, Category: Mammal

2. Princípios de encapsulamento

O encapsulamento requer a definição de campos em private e o controle do acesso de leitura/gravação por meio das propriedades public, impedindo que códigos externos modifiquem diretamente os dados internos.

▶ Exemplo

CSHARP
public class BankAccount
{
    private decimal balance;

    public decimal Balance
    {
        get { return balance; }
        set
        {
            if (value < 0)
                throw new ArgumentException("Balance cannot be negative");
            balance = value;
        }
    }
}
▶ Experimente
CSHARP
var account = new BankAccount();
account.Balance = 1000m;
Console.WriteLine($"Balance: {account.Balance}");
TEXT 📖 Somente leitura
Balance: 1000
⚠️ Definir campos como public viola diretamente o encapsulamento e impede a adição de lógica de validação posteriormente. Sempre use campos private + propriedades public.


3. Propriedades

As propriedades são wrappers seguros para campos, controlando a lógica de leitura/gravação por meio dos acessadores get e set. value é o parâmetro implícito do acessador set, representando o valor atribuído pelo chamador.

▶ Exemplo

CSHARP
public class Student
{
    private string name;

    public string Name
    {
        get { return name; }
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Name cannot be empty");
            name = value;
        }
    }
}
▶ Experimente
CSHARP
var s = new Student();
s.Name = "Alice";
Console.WriteLine(s.Name);
TEXT 📖 Somente leitura
Alice

4. Propriedades automáticas

Quando não for necessária nenhuma lógica adicional, use propriedades automáticas { get; set; }, nas quais o compilador gera automaticamente um campo de apoio oculto.

▶ Exemplo

CSHARP
public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}
▶ Experimente
CSHARP
var p = new Product { Name = "Pen", Price = 2.5m };
Console.WriteLine($"{p.Name}: ${p.Price}");
TEXT 📖 Somente leitura
Pen: $2.5
📌 As propriedades automáticas reduzem o código repetitivo. Quando for necessária uma validação, elas podem ser expandidas para propriedades completas a qualquer momento, sem afetar os chamadores.


5. O acessador init (C# 9)

O acessador init permite que uma propriedade seja atribuída durante a inicialização do objeto; a partir daí, ela passa a ser somente de leitura, possibilitando a criação de objetos imutáveis.

▶ Exemplo

CSHARP
public class Config
{
    public string Host { get; }
    public int Port { get; }
    public Config(string host, int port)
    {
        Host = host;
        Port = port;
    }
}

class Program
{
    static void Main()
    {
        var cfg = new Config("localhost", 8080);
        System.Console.WriteLine($"{cfg.Host}:{cfg.Port}");
    }
}
▶ Experimente
TEXT 📖 Somente leitura
localhost:8080
⚠️ Atribuir um valor após a inicialização causa um erro de compilação: cfg.Port = 9090; é inválido.


6. Propriedades somente leitura

Uma propriedade que contenha apenas get é uma propriedade somente leitura. Seu valor só pode ser atribuído no construtor por meio do campo de apoio.

▶ Exemplo

CSHARP
public class Circle
{
    public double Radius { get; }

    public Circle(double radius)
    {
        Radius = radius;
    }
}
▶ Experimente
CSHARP
var c = new Circle(5.0);
Console.WriteLine($"Radius: {c.Radius}");
TEXT 📖 Somente leitura
Radius: 5

7. Propriedades calculadas

As propriedades computadas não possuem campo subjacente. Elas calculam e retornam valores dinamicamente por meio de membros com corpo de expressão =>, sem armazenar dados.

▶ Exemplo

CSHARP
public class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }

    public double Area => Width * Height;
    public double Perimeter => 2 * (Width + Height);
}
▶ Experimente
CSHARP
var r = new Rectangle { Width = 3, Height = 4 };
Console.WriteLine($"Area: {r.Area}, Perimeter: {r.Perimeter}");
TEXT 📖 Somente leitura
Area: 12, Perimeter: 14

8. O modificador obrigatório (C# 11)

O modificador required obriga quem chama a função a atribuir um valor à propriedade durante a inicialização; caso contrário, ocorre um erro de compilação.

▶ Exemplo

CSHARP
public class User
{
    public string Name { get; }
    public string Email { get; }
    public int Age { get; }
    public User(string name, string email, int age)
    {
        Name = name;
        Email = email;
        Age = age;
    }
}

class Program
{
    static void Main()
    {
        var u = new User("Bob", "bob@test.com", 25);
        System.Console.WriteLine($"{u.Name} ({u.Email})");
    }
}
▶ Experimente
TEXT 📖 Somente leitura
Bob (bob@test.com)
💡 O required costuma ser usado em conjunto com o init para garantir que os campos obrigatórios dos objetos imutáveis não sejam esquecidos.


9. Introdução aos registros

record é um tipo de referência introduzido no C# 9 que oferece suporte nativo à imutabilidade e à igualdade baseada em valores, tornando-o ideal para objetos de dados puros.

▶ Exemplo

CSHARP
public class Person : IEquatable<Person>
{
    public string Name { get; }
    public int Age { get; }
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
    public bool Equals(Person other)
    {
        return other != null && Name == other.Name && Age == other.Age;
    }
    public override bool Equals(object obj) => Equals(obj as Person);
    public override int GetHashCode() => System.HashCode.Combine(Name, Age);
    public override string ToString() => $"Person {{ Name = {Name}, Age = {Age} }}";
}

class Program
{
    static void Main()
    {
        var p1 = new Person("Alice", 30);
        var p2 = new Person("Alice", 30);
        System.Console.WriteLine($"Equal: {p1 == p2}");
        System.Console.WriteLine(p1);
    }
}
▶ Experimente
CSHARP
var p1 = new Person("Alice", 30);
var p2 = new Person("Alice", 30);
Console.WriteLine($"Equal: {p1 == p2}");
Console.WriteLine(p1);
TEXT 📖 Somente leitura
Equal: True
Person { Name = Alice, Age = 30 }
🔥 A lista de parâmetros do registro gera automaticamente propriedades somente leitura init, e == compara por valor, em vez de por referência. Esse assunto será abordado em profundidade em lições posteriores.

❓ Perguntas Frequentes

P: Qual é a diferença entre protected internal e private protected? R: protected internal é uma relação “OU” (acessível por subclasses ou pelo mesmo assembly), private protected é uma relação “E” (deve ser tanto uma subclasse quanto estar no mesmo assembly).

P: Qual é a diferença entre propriedades automáticas e campos públicos? R: As propriedades automáticas possuem um campo subjacente, podem ser expandidas para adicionar lógica a qualquer momento e suportam a implementação de interfaces e a vinculação de dados, o que os campos públicos não permitem.

P: Qual é a diferença entre init e set? R: set pode ser atribuído a qualquer momento, enquanto init só pode ser atribuído durante a inicialização do objeto (construtor ou inicializador de objeto); depois disso, ele passa a ser somente de leitura.

P: É possível atribuir um valor a uma propriedade somente leitura { get; } fora do construtor? R: Não. Ela só pode ser atribuída no construtor por meio do campo de apoio e não pode ser modificada posteriormente.

P: Qual é a principal diferença entre record e class? R: record compara a igualdade por valor por padrão, suporta a expressão with para a criação de cópias e é adequado para dados imutáveis; class compara por referência.

📖 Resumo

📝 Exercícios

  1. Crie uma classe Temperature com um campo celsius definido como private; use o acessador set da propriedade Celsius para limitar a temperatura a um valor não inferior a -273,15, e defina uma propriedade calculada Fahrenheit que retorne o valor convertido
  2. Crie uma classe Book usando propriedades automáticas (Title, Author, Price), adicione uma validação para valores não negativos em set de Price (dica: expanda para uma propriedade completa)
  3. Defina uma classe ServerConfig na qual Host e Port utilizem acessadores init e sejam marcados como required; tente modificá-los após a inicialização e observe o erro de compilação
  4. Defina Point(double X, double Y) usando record, crie duas instâncias com as mesmas coordenadas e verifique se a comparação com == retorna True
  5. Crie uma classe Employee com Id como uma propriedade somente leitura (atribuída no construtor), Name utilizando required init, Salary utilizando uma propriedade set com validação e AnnualSalary como uma propriedade calculada
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%