C#: Tipos nulos e correspondência de padrões

1. Tipos de valor nulos

Os tipos de valor não podem ser null por padrão. A estrutura Nullable<T> (abreviação de T?) permite que os tipos de valor tenham semântica nula.

CSHARP
int? a = 42;
int? b = null;
bool? flag = null;
double? price = 9.99;

Console.WriteLine(a.HasValue);
Console.WriteLine(b.HasValue);
Console.WriteLine(flag.GetValueOrDefault());
Console.WriteLine(price.GetValueOrDefault(0.0));
TEXT 📖 Somente leitura
True
False
False
9.99

▶ Exemplo

CSHARP
int? score = null;

Console.WriteLine(score.HasValue);
Console.WriteLine(score.GetValueOrDefault());
Console.WriteLine(score.GetValueOrDefault(60));

score = 88;
Console.WriteLine(score.Value);
▶ Experimente
TEXT 📖 Somente leitura
False
0
60
88

2. Operadores de coalescência de nulos e operadores condicionais de nulos

?? fornece um valor padrão, ?. acessa membros com segurança e ! indica ao compilador que “isso não é nulo neste contexto”.

CSHARP
int? x = null;
int y = x ?? 0;

string? name = null;
int? len = name?.Length;

string s = null!;
Console.WriteLine(s is null);
TEXT 📖 Somente leitura
True

▶ Exemplo

CSHARP
string? input = null;

string display = input ?? "(empty)";
Console.WriteLine(display);

int? length = input?.Length;
Console.WriteLine(length ?? -1);

string? city = "Beijing";
Console.WriteLine(city?.Length ?? 0);
▶ Experimente
TEXT 📖 Somente leitura
(empty)
-1
7

3. Tipos de referência nulos

A partir do C# 8, você pode usar #nullable enable para ativar os avisos sobre tipos de referência nulos. string? permite valores nulos, enquanto string não permite.

CSHARP
#nullable enable
string? maybeNull = null;
string notNull = "hello";

maybeNull = null;
notNull = null!;

▶ Exemplo

CSHARP
#nullable enable
string Greet(string? name)
{
    return name is null ? "Hello, stranger" : $"Hello, {name}";
}

Console.WriteLine(Greet(null));
Console.WriteLine(Greet("Alice"));
▶ Experimente
TEXT 📖 Somente leitura
Hello, stranger
Hello, Alice

4. Estratégias de verificação de valores nulos

Estratégia Uso Cenário
Verificação explícita if (x is null) É necessária uma lógica de ramificação
Coalescência nula x ?? defaultValue Fornecimento de um valor padrão
Condicional nulo x?.Member Acesso seguro a membros

▶ Exemplo

CSHARP
string? GetName() => null;

string? name = GetName();

if (name is null)
{
    Console.WriteLine("Name is null");
}

string upper = name?.ToUpper() ?? "DEFAULT";
Console.WriteLine(upper);
▶ Experimente
TEXT 📖 Somente leitura
Name is null
DEFAULT

5. Introdução à correspondência de padrões

A correspondência de padrões combina a verificação de tipos e a extração de variáveis em um único processo, sendo comumente utilizada com is e switch.

(1) é o padrão

CSHARP
object obj = 42;

if (obj is int i)
{
    Console.WriteLine($"Is integer: {i}");
}
TEXT 📖 Somente leitura
Is integer: 42

▶ Exemplo

CSHARP
object value = "hello";

if (value is int n)
{
    Console.WriteLine(n);
}
else if (value is string s)
{
    Console.WriteLine($"String length: {s.Length}");
}
▶ Experimente
TEXT 📖 Somente leitura
String length: 5

6. Padrão de tipos e switch

Classifique por tipo em switch e extraia as variáveis.

▶ Exemplo

CSHARP
string Describe(object obj)
{
    return obj switch
    {
        int i => $"Integer {i}",
        double d => $"Double {d}",
        string s => $"String \"{s}\"",
        bool b => $"Boolean {b}",
        null => "null",
        _ => "Unknown type"
    };
}

Console.WriteLine(Describe(10));
Console.WriteLine(Describe(3.14));
Console.WriteLine(Describe("hi"));
Console.WriteLine(Describe(true));
▶ Experimente
TEXT 📖 Somente leitura
Integer 10
Double 3.14
String "hi"
Boolean True

7. Padrão de Propriedade

Os padrões de propriedades são comparados com base nas propriedades dos objetos, sem a necessidade de extrair manualmente os valores para comparação.

▶ Exemplo

CSHARP
var person = new { Name = "Alice", Age = 20 };

if (person is { Age: > 18 })
{
    Console.WriteLine("Adult");
}

string Category(object p) => p switch
{
    { Age: < 12 } => "Child",
    { Age: >= 12 and < 18 } => "Teenager",
    { Age: >= 18 } => "Adult",
    _ => "Unknown"
};

Console.WriteLine(Category(person));
▶ Experimente
TEXT 📖 Somente leitura
Adult
Adult

8. Tipo de registro

O C# 9 introduziu o record, que oferece comparação de igualdade baseada em valores e imutabilidade por padrão.

CSHARP
record Person(string Name, int Age);

▶ Exemplo

CSHARP
record Person(string Name, int Age);

var p1 = new Person("Alice", 18);
var p2 = new Person("Alice", 18);

Console.WriteLine(p1 == p2);
Console.WriteLine(ReferenceEquals(p1, p2));
Console.WriteLine(p1);
▶ Experimente
TEXT 📖 Somente leitura
True
False
Person { Name = Alice, Age = 18 }

9. com a Expression

with cria uma cópia de um registro existente com algumas propriedades modificadas, mantendo a instância original inalterada.

▶ Exemplo

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

class Program
{
    static void Main()
    {
        var s1 = new Student("Bob", 20, "A");
        var s2 = s1.With(age: 21, grade: "A+");
        System.Console.WriteLine(s1);
        System.Console.WriteLine(s2);
    }
}
▶ Experimente
TEXT 📖 Somente leitura
Student { Name = Bob, Age = 20, Grade = A }
Student { Name = Bob, Age = 21, Grade = A+ }

10. Tupla

As tuplas combinam vários valores em uma estrutura leve, permitindo o uso de elementos nomeados e a desconstrução.

▶ Exemplo

CSHARP
(int Id, string Name) person = (1, "Charlie");
Console.WriteLine(person.Id);
Console.WriteLine(person.Name);

var coords = (X: 3.0, Y: 4.0);
double distance = Math.Sqrt(coords.X * coords.X + coords.Y * coords.Y);
Console.WriteLine(distance);

(int x, int y) = (10, 20);
Console.WriteLine($"x={x}, y={y}");
▶ Experimente
TEXT 📖 Somente leitura
1
Charlie
5
x=10, y=20

11. Desconstrução de tuplas e correspondência de padrões

A combinação de tuplas com a correspondência de padrões permite lidar com ramificações com múltiplas condições de maneira elegante.

▶ Exemplo

CSHARP
string Classify(int score, bool hasBonus) => (score, hasBonus) switch
{
    (>= 90, true) => "Excellent+Bonus",
    (>= 90, false) => "Excellent",
    (>= 60, true) => "Pass+Bonus",
    (>= 60, false) => "Pass",
    _ => "Fail"
};

Console.WriteLine(Classify(95, true));
Console.WriteLine(Classify(75, false));
Console.WriteLine(Classify(50, true));
▶ Experimente
TEXT 📖 Somente leitura
Excellent+Bonus
Pass
Fail

❓ Perguntas Frequentes

P: É possível usar int? e int diretamente em operações aritméticas? R: Não. É preciso convertê-los para um valor diferente de nulo usando .Value ou ?? antes de realizar a operação.

P: Qual é a diferença entre string? e string? R: string? permite a atribuição de null, enquanto string gera um aviso do compilador quando se atribui null com a opção “nullable” ativada.

P: Qual é a principal diferença entre record e class? R: O record utiliza a igualdade baseada em valor (== compara o conteúdo), enquanto a class utiliza a igualdade baseada em referência (== compara o endereço de memória).

P: A expressão with pode ser usada com class? R: Não. A expressão with só funciona com tipos de registro ou estruturas com propriedades de inicialização exclusiva.

P: Existe um limite para o número de elementos de uma tupla? R: O ValueTuple suporta até 8 parâmetros de tipo; além disso, utiliza-se TRest aninhado para extensão.

📖 Resumo

📝 Exercícios

  1. Declare double? price = null;; use GetValueOrDefault(99.9) para obter o valor padrão e exibi-lo
  2. Escreva um método que aceite um parâmetro string?, utilize ?. e ?? para retornar o comprimento da string ou -1
  3. Use o padrão is para verificar se object obj = 3.14; é igual a double d e exiba d * 2
  4. Defina record Book(string Title, double Price);, crie uma instância e use with para modificar o preço
  5. Use uma expressão switch com tupla para verificar se (int month, int day) corresponde a “Dia de Ano Novo” ou “Dia Nacional”
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%