C#: Visão geral dos recursos avançados
1. Reflexão
A reflexão permite que um programa inspecione e manipule suas próprias informações de tipo em tempo de execução. A classe Type é o núcleo da reflexão, fornecendo acesso a metadados como campos, propriedades e métodos.
(1) Obtendo informações sobre tipos
using System;
using System.Reflection;
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void SayHello() => Console.WriteLine("Hello!");
}
class Program
{
static void Main()
{
Type type1 = typeof(Person);
Type type2 = new Person().GetType();
Console.WriteLine($"Type name: {type1.Name}");
Console.WriteLine("Properties:");
foreach (PropertyInfo p in type1.GetProperties())
Console.WriteLine($" {p.Name} ({p.PropertyType.Name})");
Console.WriteLine("Methods:");
foreach (MethodInfo m in type1.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
Console.WriteLine($" {m.Name}");
}
}
Type name: Person
Properties:
Name (String)
Age (Int32)
Methods:
get_Name
set_Name
get_Age
set_Age
SayHello
(2) Criação dinâmica de instâncias
using System;
using System.Reflection;
class Dog
{
public string Breed { get; set; } = "Unknown";
public override string ToString() => $"Dog: {Breed}";
}
class Program
{
static void Main()
{
Type type = typeof(Dog);
object obj = Activator.CreateInstance(type)!;
PropertyInfo prop = type.GetProperty("Breed")!;
prop.SetValue(obj, "Husky");
Console.WriteLine(obj);
}
}
Dog: Husky
▶ Exemplo
using System;
using System.Linq;
using System.Reflection;
class Calculator
{
public int Add(int a, int b) => a + b;
public double Multiply(double x, double y) => x * y;
public void Reset() { }
}
class Program
{
static void Main()
{
Calculator calc = new Calculator();
Type type = calc.GetType();
Console.WriteLine($"Type: {type.Name}");
Console.WriteLine($"Namespace: {type.Namespace}");
Console.WriteLine($"Is class: {type.IsClass}");
Console.WriteLine("Public methods:");
MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (MethodInfo m in methods)
{
string parameters = string.Join(", ", m.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
Console.WriteLine($" {m.ReturnType.Name} {m.Name}({parameters})");
}
}
}
Type: Calculator
Namespace:
Is class: True
Public methods:
Int32 Add(Int32 a, Int32 b)
Double Multiply(Double x, Double y)
Void Reset()
2. Atributos
Atributos são marcadores declarativos usados para adicionar metadados a elementos de código. É possível usar atributos embutidos ou definir atributos personalizados.
(1) Atributos embutidos
using System;
class LegacyService
{
[Obsolete("Please use NewService instead", error: false)]
public void OldMethod() => Console.WriteLine("Old method");
public void NewMethod() => Console.WriteLine("New method");
}
class Program
{
static void Main()
{
var service = new LegacyService();
service.OldMethod();
service.NewMethod();
}
}
Old method
New method
error de Obsolete for definido como true, chamar o método antigo resultará em um erro de compilação, em vez de um aviso.
(2) Atributos personalizados
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
class DescriptionAttribute : Attribute
{
public string Text { get; }
public DescriptionAttribute(string text) => Text = text;
}
[Description("User management service")]
class UserService
{
[Description("Get all users")]
public void GetAll() { }
}
class Program
{
static void Main()
{
var attr = Attribute.GetCustomAttribute(typeof(UserService), typeof(DescriptionAttribute)) as DescriptionAttribute;
Console.WriteLine(attr?.Text ?? "No description");
}
}
User management service
3. Restrições genéricas em detalhes
As restrições genéricas utilizam a palavra-chave where para restringir o intervalo dos parâmetros de tipo, garantindo que eles atendam a condições específicas.
(1) Visão geral dos tipos de restrição
| Restrição | Descrição |
|---|---|
where T : class |
T deve ser um tipo de referência |
where T : struct |
T deve ser um tipo de valor (não pode ser nulo) |
where T : new() |
T deve ter um construtor público sem parâmetros |
where T : ISpecific |
T deve implementar a interface especificada |
where T : BaseClass |
T deve herdar da classe base especificada |
where T : unmanaged |
T deve ser um tipo não gerenciado |
where T : notnull |
T deve ser um tipo que não pode ser nulo |
▶ Exemplo
using System;
interface IRepository
{
string GetName();
}
class SqlRepository : IRepository
{
public string GetName() => "SQL Repository";
}
class Service```<T>``` where T : class, IRepository, new()
{
private T _repo = new T();
public void Print() => Console.WriteLine(_repo.GetName());
}
class Program
{
static void Main()
{
var service = new Service<SqlRepository>();
service.Print();
}
}
SQL Repository
new() deve aparecer por último na lista de restrições; class ou struct devem vir primeiro.
4. Iteradores (yield)
Os iteradores utilizam yield return para gerar os elementos da sequência um por um, implementando a avaliação preguiçosa — os elementos são calculados somente quando iterados.
(1) rendimento e quebra de rendimento
using System;
using System.Collections.Generic;
class Program
{
static IEnumerable```<int>``` GetNumbers()
{
for (int i = 0; i < 5; i++)
{
if (i == 3) yield break;
yield return i;
}
}
static void Main()
{
foreach (int n in GetNumbers())
Console.WriteLine(n);
}
}
0
1
2
yield break encerra imediatamente a iteração. Os métodos do iterador retornam IEnumerable<T> ou IEnumerator<T>.
▶ Exemplo
using System;
using System.Collections.Generic;
class Program
{
static IEnumerable```<int>``` Fibonacci(int count)
{
int a = 0;
int b = 1;
for (int i = 0; i < count; i++)
{
yield return a;
int temp = a + b;
a = b;
b = temp;
}
}
static void Main()
{
Console.WriteLine("Fibonacci sequence, first 10 terms:");
foreach (int n in Fibonacci(10))
{
Console.Write(n + " ");
}
Console.WriteLine();
}
}
Fibonacci sequence, first 10 terms:
0 1 1 2 3 5 8 13 21 34
5. Span<T> e Memory<T>
Span<T> e Memory<T> oferecem acesso segmentado, sem cópia, a regiões contíguas de memória, o que os torna tipos essenciais para cenários de alto desempenho.
(1) Span<T> Uso básico
using System;
class Program
{
static void Main()
{
int[] array = { 10, 20, 30, 40, 50 };
Span```<int>``` span = array.AsSpan();
Span```<int>``` slice = span.Slice(1, 3);
slice[0] = 99;
Console.WriteLine(string.Join(", ", array));
Span```<int>``` stackSpan = stackalloc int[3];
stackSpan[0] = 1;
stackSpan[1] = 2;
stackSpan[2] = 3;
Console.WriteLine(string.Join(", ", stackSpan.ToArray()));
}
}
10, 99, 30, 40, 50
1, 2, 3
Span<T> só pode ser armazenado na pilha; não pode ser usado como campo de classe nem além de um limite de await. Use Memory<T> quando precisar abranger vários quadros da pilha.
6. Expressões regulares (Regex)
A classe System.Text.RegularExpressions.Regex é usada para correspondência de padrões, pesquisa e substituição de texto.
(1) Operações comuns
using System;
using System.Text.RegularExpressions;
class Program
{
static void Main()
{
string input = "Contact phone: 138-1234-5678, Email: test@example.com";
bool hasPhone = Regex.IsMatch(input, @"\d{3}-\d{4}-\d{4}");
Console.WriteLine($"Contains phone: {hasPhone}");
Match phoneMatch = Regex.Match(input, @"\d{3}-\d{4}-\d{4}");
Console.WriteLine($"Phone: {phoneMatch.Value}");
MatchCollection emails = Regex.Matches(input, @"[\w.]+@[\w.]+");
foreach (Match m in emails)
Console.WriteLine($"Email: {m.Value}");
string replaced = Regex.Replace(input, @"\d{4}", "****");
Console.WriteLine($"Replaced: {replaced}");
}
}
Contains phone: True
Phone: 138-1234-5678
Email: test@example.com
Replaced: Contact phone: 138-****-****, Email: test@example.com
(2) Referência rápida aos padrões comuns
| Padrão | Descrição |
|---|---|
\d |
Dígito |
\w |
Letra/dígito/traço inferior |
\s |
Espaço em branco |
. |
Qualquer caractere (exceto quebra de linha) |
^ / $ |
Início da linha / Fim da linha |
{n,m} |
Repita n a m vezes |
[abc] |
Conjunto de caracteres |
7. Código inseguro
O contexto unsafe permite o uso de ponteiros e operações manuais de memória, aplicável em cenários de interoperabilidade ou de desempenho extremo.
(1) Ponteiros e valores fixos
using System;
class Program
{
static unsafe void Main()
{
int value = 42;
int* ptr = &value;
Console.WriteLine($"Value: {*ptr}");
Console.WriteLine($"Address: {(long)ptr}");
int[] arr = { 1, 2, 3 };
fixed (int* p = arr)
{
Console.WriteLine($"First element: {*p}");
Console.WriteLine($"Second element: {*(p + 1)}");
}
}
}
Value: 42
Address: 12345678
First element: 1
Second element: 2
unsafe requer que <AllowUnsafeBlocks> seja ativado; a instrução true`` in the project file. The fixed fixa um objeto para impedir que o GC mova a memória.
8. Visão geral dos recursos das versões do C# (7–12)
| Versão | Principais recursos | Breve descrição |
|---|---|---|
| C# 7 | variáveis out, tuplas, correspondência de padrões |
out int x declaração inline, (int, string) tuplas, is Type padrão |
| C# 7 | funções locais, retorno por referência | funções aninhadas em métodos, valores de retorno por referência |
| C# 8 | tipos de referência nulos, fluxos assíncronos | string? anotação de nulidade, IAsyncEnumerable<T> |
| C# 8 | métodos padrão de interface, índices e intervalos | Implementações padrão de interface, [^1], [1..3] |
| C# 9 | tipos de registro, setter de inicialização | record tipos imutáveis, init propriedades somente de inicialização |
| C# 9 | instruções de nível superior, new com tipo de destino |
Sem código padrão para Main, new() omissão de tipo |
| C# 10 | uso global de “using”, namespaces com escopo de arquivo | global using X;, namespace Ns; em uma única linha |
| C# 10 | estruturas de registro, strings interpoladas com constantes | record struct, strings interpoladas com constantes |
| C# 11 | literais de string bruta, obrigatórios | """...""" strings brutas com várias linhas, required inicialização forçada |
| C# 11 | padrões de lista, literais UTF-8 | correspondência [1, 2, ..], "hello"u8 |
| C# 12 | construtores primários | class C(int x) declaração de classe como construtor |
| C# 12 | expressões de coleção, alias para qualquer tipo | [1, 2, 3] inicialização uniforme, using P = int* |
❓ Perguntas Frequentes
P: A reflexão tem um custo de desempenho? R: Sim, a reflexão é dezenas de vezes mais lenta do que chamadas diretas. Para uso frequente, armazene o MethodInfo em cache ou mude para árvores de expressão. P: Qual é a diferença entre Span
<T>e Memory<T>? R: O Span só pode ser mantido na pilha e não pode ultrapassar os limites de assíncrono/armazenamento; o Memory pode ser mantido no heap e ultrapassar os limites de assíncrono, sendo acessado por meio de .Span. P: O retorno doyieldé multithread? R: Não, oyieldé um mecanismo de enumeração preguiçosa de thread único — os elementos são calculados somente quando oMoveNext()é chamado. P: Quando é necessário usar código não seguro? R: Use-o quando precisar de interoperabilidade com C/C++, operações de memória não gerenciada ou desempenho extremo; evite-o no desenvolvimento cotidiano.
📖 Resumo
- A reflexão acessa os metadados de tipos em tempo de execução por meio da classe
Typee pode criar instâncias dinamicamente - Os atributos adicionam metadados declarativos ao código, permitindo definições personalizadas e a leitura em tempo de execução
- As restrições genéricas (where) limitam os parâmetros de tipo a tipos de referência, tipos de valor, tipos com construtores, etc.
yield returnimplementa a iteração preguiçosa;yield breakencerra a enumeração- O Span
<T>oferece segmentação com cópia zero baseada em pilha; o Memory<T>pode atravessar limites assíncronos - A classe Regex oferece suporte à correspondência de padrões, pesquisa, substituição e outras operações de texto
- O contexto não seguro permite operações com ponteiros, exigindo que a memória seja fixada
- O C# 7–12 introduziu, de forma contínua, tuplas, referências nulas, tipos de registro, construtores primários e muito mais
📝 Exercícios
- Use a reflexão para obter todos os nomes dos métodos públicos da classe
stringe exibi-los - Criar um atributo personalizado
[Author], aplicá-lo a uma classe e lê-lo por meio de reflexão - Escreva um método genérico
T Create``<T>``() where T : new()que crie e retorne uma instância - Use o
yield returnpara implementar um iterador que gere os primeiros 20 termos da sequência de Fibonacci - Use expressões regulares (Regex) para verificar se uma string está no formato válido de um endereço IPv4