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.
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));
True
False
False
9.99
▶ Exemplo
int? score = null;
Console.WriteLine(score.HasValue);
Console.WriteLine(score.GetValueOrDefault());
Console.WriteLine(score.GetValueOrDefault(60));
score = 88;
Console.WriteLine(score.Value);
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”.
int? x = null;
int y = x ?? 0;
string? name = null;
int? len = name?.Length;
string s = null!;
Console.WriteLine(s is null);
True
▶ Exemplo
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);
(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.
#nullable enable
string? maybeNull = null;
string notNull = "hello";
maybeNull = null;
notNull = null!;
▶ Exemplo
#nullable enable
string Greet(string? name)
{
return name is null ? "Hello, stranger" : $"Hello, {name}";
}
Console.WriteLine(Greet(null));
Console.WriteLine(Greet("Alice"));
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
string? GetName() => null;
string? name = GetName();
if (name is null)
{
Console.WriteLine("Name is null");
}
string upper = name?.ToUpper() ?? "DEFAULT";
Console.WriteLine(upper);
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
object obj = 42;
if (obj is int i)
{
Console.WriteLine($"Is integer: {i}");
}
Is integer: 42
▶ Exemplo
object value = "hello";
if (value is int n)
{
Console.WriteLine(n);
}
else if (value is string s)
{
Console.WriteLine($"String length: {s.Length}");
}
String length: 5
6. Padrão de tipos e switch
Classifique por tipo em switch e extraia as variáveis.
▶ Exemplo
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));
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
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));
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.
record Person(string Name, int Age);
▶ Exemplo
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);
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
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);
}
}
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
(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}");
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
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));
Excellent+Bonus
Pass
Fail
❓ Perguntas Frequentes
P: É possível usar
int?eintdiretamente em operações aritméticas? R: Não. É preciso convertê-los para um valor diferente de nulo usando.Valueou??antes de realizar a operação.
P: Qual é a diferença entre
string?estring? R:string?permite a atribuição denull, enquantostringgera um aviso do compilador quando se atribuinullcom 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
withpode ser usada comclass? R: Não. A expressãowithsó 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
T?éNullable<T>, permitindo que tipos de valor possam ser nulosHasValue/Value/GetValueOrDefault()acessam valores nulos??,?.,!lidam com cenários nulos#nullable enableativa a verificação de tipos de referência nulos- O padrão
is, o padrão de tipo e o padrão de propriedade simplificam a verificação de tipos switchAs expressões com correspondência de padrões expressam de forma concisa a lógica com múltiplas ramificaçõesrecordgarante a igualdade de valores e a imutabilidadewithcria cópias de registros com algumas propriedades modificadas- As tuplas combinam vários valores de maneira leve, permitindo a atribuição de nomes e a desconstrução
📝 Exercícios
- Declare
double? price = null;; useGetValueOrDefault(99.9)para obter o valor padrão e exibi-lo - Escreva um método que aceite um parâmetro
string?, utilize?.e??para retornar o comprimento da string ou-1 - Use o padrão
ispara verificar seobject obj = 3.14;é igual adouble de exibad * 2 - Defina
record Book(string Title, double Price);, crie uma instância e usewithpara modificar o preço - Use uma expressão switch com tupla para verificar se
(int month, int day)corresponde a “Dia de Ano Novo” ou “Dia Nacional”