C# Reflexão

Normalmente, o programa usa as classes que você escreveu sem "saber" nada sobre elas: chama produto.Nome porque você escreveu produto.Nome. Com a reflexão (em inglês, reflection), o programa se olha no espelho enquanto roda: pergunta "de que tipo é este objeto?", "que propriedades ele tem?", "existe um método chamado Somar?", e pode até chamar esse método a partir de um texto. É assim que funcionam por dentro o System.Text.Json, os testes do xUnit e muitas outras bibliotecas.

Você vai aprender
  • O que é um Type e como obtê-lo com typeof e GetType().
  • Listar as propriedades de qualquer objeto e ler os valores (GetProperties, GetValue).
  • Achar um método pelo nome e chamá-lo (GetMethod, Invoke), e criar um objeto a partir do nome da classe.
  • Ler atributos para montar um validador de cadastro.
  • Por que a reflexão abre até campos private, por que ela é lenta e o que são os avisos IL2075 e IL2026.
  • As exceções NullReferenceException, TargetParameterCountException e TargetInvocationException.

Esta aula usa classes, propriedades, atributos e os operadores ?. e ??. Os exemplos rodaram com dotnet run no .NET 10 da máquina da Ana, na pasta ~/cs.


A ideia antes do código

Quando o C# compila o seu programa, ele não guarda só as instruções. Guarda também uma ficha de cada tipo: o nome da classe, as propriedades (com nome e tipo), os métodos (com os parâmetros), os atributos. Essa ficha se chama metadados ("dados sobre os dados").

Pense na ficha cadastral de um produto na lojinha da Ana: ela não é o produto, mas descreve tudo sobre ele. A reflexão é o jeito de o programa ler essas fichas enquanto roda. Com ela, dá para escrever um único método que mostra qualquer objeto na tela, sem saber de antemão de que classe ele é.


1. Type: a ficha de um tipo

A ficha é um objeto da classe Type (tipo). Há dois jeitos de pegá-la: typeof(Classe), quando você sabe o nome da classe ao escrever o código, e objeto.GetType(), quando você tem um objeto e quer saber o que ele é de verdade.

Produto caderno = new("Caderno", 15.90m, 12);

Type tipo = caderno.GetType();
Console.WriteLine(tipo.Name);
Console.WriteLine(tipo.FullName);
Console.WriteLine(tipo == typeof(Produto));
Console.WriteLine(tipo.BaseType);
Console.WriteLine(typeof(int).FullName);
Console.WriteLine(typeof(List<string>).Name);
Console.WriteLine(42.GetType().Name);
object coisa = "Ana";
Console.WriteLine(coisa.GetType().Name);

record Produto(string Nome, decimal Preco, int Estoque);
$ dotnet run espelho.cs
Produto
Produto
True
System.Object
System.Int32
List`1
Int32
String
LinhaSaídaPor quê
caderno.GetType()—Pede a ficha do objeto. Todo objeto tem esse método, porque ele vem de object.
tipo.Name / tipo.FullNameProdutoO nome curto e o nome com o namespace. Um record solto no arquivo não tem namespace, então os dois são iguais.
tipo == typeof(Produto)TrueExiste uma ficha por tipo; as duas formas devolvem o mesmo objeto Type.
tipo.BaseTypeSystem.ObjectDe quem o tipo herda (veja herança).
typeof(int).FullNameSystem.Int32int é o apelido em C# do tipo System.Int32 do .NET.
typeof(List<string>).NameList`1O nome "de verdade" de um tipo genérico: List, uma crase e o número de parâmetros de tipo.
coisa.GetType().NameStringA variável é object, mas o objeto dentro dela é um texto. GetType() sempre conta o tipo real.

2. Listar as propriedades de qualquer objeto

A ficha sabe quais propriedades o tipo tem. GetProperties() devolve uma lista de PropertyInfo ("informação de propriedade"), e cada uma sabe o próprio nome, o tipo e como ler o valor num objeto. Com isso, a Ana escreve um Mostrar que funciona para produtos, clientes e qualquer outra classe:

using System.Reflection;

Mostrar(new Produto("Caderno", 15.90m, 12));
Mostrar(new Cliente { Nome = "Ana", Cidade = "Recife", Idade = 29 });

void Mostrar(object obj)
{
    Type tipo = obj.GetType();
    Console.WriteLine($"--- {tipo.Name} ---");
    foreach (PropertyInfo p in tipo.GetProperties())
    {
        object? valor = p.GetValue(obj);
        Console.WriteLine($"{p.Name} ({p.PropertyType.Name}) = {valor}");
    }
}

record Produto(string Nome, decimal Preco, int Estoque);

class Cliente
{
    public string Nome { get; set; } = "";
    public string Cidade { get; set; } = "";
    public int Idade { get; set; }
}
$ dotnet run mostrar.cs
/home/ana/cs/mostrar.cs(10,32): warning IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties' in call to 'System.Type.GetProperties()'. The return value of method 'System.Object.GetType()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
--- Produto ---
Nome (String) = Caderno
Preco (Decimal) = 15,90
Estoque (Int32) = 12
--- Cliente ---
Nome (String) = Ana
Cidade (String) = Recife
Idade (Int32) = 29
using System.Reflection;
A pasta das classes de reflexão: PropertyInfo, MethodInfo, FieldInfo…
tipo.GetProperties()
As propriedades públicas do tipo, na ordem em que foram declaradas.
p.Name
O nome da propriedade, como texto: "Preco".
p.PropertyType.Name
O tipo dela (que também é um Type): Decimal.
p.GetValue(obj)
"Leia esta propriedade neste objeto." Devolve object?, porque a reflexão não sabe o tipo na hora de compilar.

O programa funcionou, mas o dotnet deu um aviso em inglês, o IL2075. Ele não é do compilador C#, e sim de um analisador que pensa no futuro: um programa pode ser publicado "enxugado" (trimming), com o que não é usado arrancado para ficar menor. Num programa assim, o analisador não consegue garantir que as propriedades de "um objeto qualquer" (obj.GetType()) vão continuar lá. Veja a seção 6.

No editor do guia

O editor desta página roda os mesmos analisadores do dotnet run arquivo.cs: o aviso IL2075 aparece na caixa, igualzinho, e o resultado do programa também é o mesmo. Conferido com o testador do guia.

Missão: complete o Mostrar para listar cada propriedade no formato Nome = Caneta, usando GetProperties() e GetValue.

using System.Reflection;

Mostrar(new Caneta("Caneta", 2.50m, "azul"));

void Mostrar(object obj)
{
    Type tipo = obj.GetType();
    Console.WriteLine($"Um objeto do tipo {tipo.Name}");
    // percorra tipo.GetProperties() e mostre "Nome = valor"
}

record Caneta(string Nome, decimal Preco, string Cor);
using System.Reflection;

Mostrar(new Caneta("Caneta", 2.50m, "azul"));

void Mostrar(object obj)
{
    Type tipo = obj.GetType();
    Console.WriteLine($"Um objeto do tipo {tipo.Name}");
    foreach (PropertyInfo p in tipo.GetProperties())
    {
        Console.WriteLine($"{p.Name} = {p.GetValue(obj)}");
    }
}

record Caneta(string Nome, decimal Preco, string Cor);

3. Achar e chamar um método pelo nome

A ficha também lista os métodos. E o mais curioso: dá para chamar um método cujo nome só se conhece enquanto o programa roda, por exemplo um nome digitado pelo usuário ou lido de um arquivo.

using System.Reflection;

Calculadora calc = new();
Type tipo = typeof(Calculadora);

foreach (MethodInfo m in tipo.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
    string parametros = string.Join(", ", m.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
    Console.WriteLine($"{m.ReturnType.Name} {m.Name}({parametros})");
}

string? escolha = "Multiplicar";
MethodInfo? metodo = tipo.GetMethod(escolha);
object? resultado = metodo?.Invoke(calc, [6, 7]);
Console.WriteLine($"{escolha}(6, 7) = {resultado}");

class Calculadora
{
    public int Somar(int a, int b) => a + b;
    public int Multiplicar(int a, int b) => a * b;
    public string Descrever() => "Calculadora da Ana";
}
$ dotnet run metodos.cs
Int32 Somar(Int32 a, Int32 b)
Int32 Multiplicar(Int32 a, Int32 b)
String Descrever()
Multiplicar(6, 7) = 42
TrechoO que faz
GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)Os BindingFlags ("opções de busca") filtram a lista: métodos públicos, de instância (não static) e declarados nesta classe. Sem o DeclaredOnly, viriam também os herdados de object: ToString, Equals, GetHashCode, GetType.
m.GetParameters()Os parâmetros do método, cada um com nome e tipo.
tipo.GetMethod(escolha)Procura um método público com esse nome. Se não existir, devolve null.
metodo?.Invoke(calc, [6, 7])Chama o método no objeto calc, com os argumentos num array de object. O resultado vem como object?. O ?. evita o erro se o método não existir.

Criar um objeto a partir do nome

Type.GetType("Nome") procura um tipo pelo nome, e Activator.CreateInstance ("ativador, criar instância") faz um new nele:

Type? tipo = Type.GetType("Caderno");
Console.WriteLine(tipo?.Name ?? "não achei");
object? obj = Activator.CreateInstance(tipo!);
Console.WriteLine(obj);
Console.WriteLine(Type.GetType("Lapis")?.Name ?? "não achei");
class Caderno { public override string ToString() => "um caderno novo"; }
$ dotnet run criar.cs
Caderno
um caderno novo
não achei

Para um tipo que está dentro de um namespace, o nome completo é necessário: Type.GetType("Loja.Caderno"). Esse é o truque por trás de "plugins": o nome da classe vem de um arquivo de configuração, e o programa cria o objeto sem que o código mencione a classe.


4. Ler atributos: um validador de cadastro

Na aula de atributos, você viu que uma etiqueta só faz efeito quando alguém a lê. Agora você é esse alguém. A Ana cria duas etiquetas, [Obrigatorio] e [Faixa(min, max)], cola nas propriedades do Cadastro, e um Validar confere tudo lendo as etiquetas:

using System.Reflection;

Cadastro bom = new() { Nome = "Ana", Email = "ana@email.com", Idade = 29 };
Cadastro ruim = new() { Nome = "", Email = null, Idade = 200 };

Validar(bom);
Validar(ruim);

void Validar(object obj)
{
    Console.WriteLine($"Validando {obj.GetType().Name}...");
    int problemas = 0;
    foreach (PropertyInfo p in typeof(Cadastro).GetProperties())
    {
        object? valor = p.GetValue(obj);
        if (p.GetCustomAttribute<ObrigatorioAttribute>() is not null
            && (valor is null || valor is ""))
        {
            Console.WriteLine($"  {p.Name}: obrigatório");
            problemas++;
        }
        if (p.GetCustomAttribute<FaixaAttribute>() is { } faixa
            && valor is int n && (n < faixa.Minimo || n > faixa.Maximo))
        {
            Console.WriteLine($"  {p.Name}: {n} fora de {faixa.Minimo} a {faixa.Maximo}");
            problemas++;
        }
    }
    Console.WriteLine(problemas == 0 ? "  tudo certo" : $"  {problemas} problema(s)");
}

[AttributeUsage(AttributeTargets.Property)]
class ObrigatorioAttribute : Attribute { }

[AttributeUsage(AttributeTargets.Property)]
class FaixaAttribute(int minimo, int maximo) : Attribute
{
    public int Minimo { get; } = minimo;
    public int Maximo { get; } = maximo;
}

class Cadastro
{
    [Obrigatorio] public string Nome { get; set; } = "";
    [Obrigatorio] public string? Email { get; set; }
    [Faixa(0, 130)] public int Idade { get; set; }
}
$ dotnet run validar.cs
Validando Cadastro...
  tudo certo
Validando Cadastro...
  Nome: obrigatório
  Email: obrigatório
  Idade: 200 fora de 0 a 130
  3 problema(s)
p.GetCustomAttribute<ObrigatorioAttribute>()
"Esta propriedade tem a etiqueta [Obrigatorio]?" Devolve o atributo ou null.
valor is null || valor is ""
Obrigatório e vazio: um problema.
is { } faixa
Um padrão de propriedade vazio: "não é nulo, e chame-o de faixa". Assim dá para ler faixa.Minimo e faixa.Maximo.

Repare na divisão de trabalho: o Cadastro só declara as regras, com etiquetas, e o Validar as aplica. Se amanhã a Ana criar uma classe Fornecedor, basta etiquetar as propriedades. É exatamente assim que funcionam os [Required] e [Range] do ASP.NET.


5. A reflexão abre até o que é private

Com os BindingFlags certos, a reflexão enxerga e altera campos privados:

using System.Reflection;

Conta conta = new();
Console.WriteLine(conta);

FieldInfo? campo = typeof(Conta).GetField("_saldo", BindingFlags.NonPublic | BindingFlags.Instance);
campo?.SetValue(conta, 1_000_000m);
Console.WriteLine(conta);

class Conta
{
    private decimal _saldo = 50m;
    public override string ToString() => $"Saldo: {_saldo:N2}";
}
$ dotnet run privado.cs
Saldo: 50,00
Saldo: 1.000.000,00
private não é cadeado

O private protege o código de erros de quem programa, não de alguém mal-intencionado: qualquer código rodando no mesmo programa pode, com reflexão, mexer no que quiser. Nunca guarde uma senha num campo private achando que está segura. E não use esse truque no dia a dia: ele passa por cima do encapsulamento, e o seu programa quebra quando o autor da classe renomear o campo (o nome "_saldo" é só um texto, o compilador não confere). O uso legítimo é em ferramentas e testes.


6. O preço da reflexão

CustoO que acontece
Sem ajuda do compiladorOs nomes são textos: GetMethod("Multplicar") (com erro de digitação) compila e só falha ao rodar. O nameof(Calculadora.Multiplicar) ajuda: ele é conferido pelo compilador.
LentidãoProcurar um método e chamá-lo por Invoke custa bem mais que uma chamada normal (cada argumento vira object, há conferências a cada chamada). Num laço de milhões de voltas, faz diferença.
Programas enxugadosAo publicar com trimming ou Native AOT (veja Publicar o Programa), o que o compilador não vê sendo usado pode ser removido, e a reflexão pode não achar o que procura. Os avisos IL2075 e IL2026 avisam desses pontos.
Por que o dotnet run arquivo.cs mostra o IL2075?

Os programas de um arquivo só do .NET 10 (dotnet run arquivo.cs) já vêm preparados para serem publicados com Native AOT, e por isso os analisadores de trimming ficam ligados. O programa roda normalmente; o aviso é sobre uma futura publicação enxugada. Se o seu programa usa reflexão de propósito e você não pretende publicá-lo assim, desligue o modo AOT com uma linha no topo do arquivo: #:property PublishAot=false. Conferido no laboratório: com ela, o mostrar.cs roda sem o aviso. Num projeto (dotnet new console), o AOT vem desligado e o aviso não aparece.

Por isso, a regra prática: use reflexão quando não existe outro jeito, como em ferramentas genéricas (mostrar, salvar, validar qualquer objeto). Se você sabe o tipo, use o tipo: produto.Nome é mais rápido, mais seguro e o compilador confere. Muitas bibliotecas modernas trocaram a reflexão por geradores de código, que escrevem o código "normal" na hora de compilar (o System.Text.Json tem um, que a aula JSON menciona).


Qual usar?

Você quer…Use
A ficha de uma classe que você conhece pelo nome no códigotypeof(Produto)
Saber o tipo real de um objetoobj.GetType() (para só testar o tipo, prefira obj is Produto p)
O nome de algo, conferido pelo compiladornameof(Produto.Preco), não reflexão
Percorrer as propriedades de um objeto qualquerGetProperties() + GetValue
Chamar um método cujo nome vem de foraGetMethod(nome)?.Invoke(obj, args), ou, melhor, um Dictionary<string, Func<…>> (veja Func e Action)
Regras declaradas com etiquetasAtributos + GetCustomAttribute

Quando dá errado

O método não existe: NullReferenceException

Este código tem um erro de propósito.
Calculadora calc = new();
var metodo = typeof(Calculadora).GetMethod("Dividir");
Console.WriteLine(metodo.Invoke(calc, [6, 0]));
class Calculadora
{
    public int Somar(int a, int b) => a + b;
}
$ dotnet run semmetodo.cs
/home/ana/cs/semmetodo.cs(3,19): warning CS8602: Desreferência de uma referência possivelmente nula.
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at Program.<Main>$(String[] args) in /home/ana/cs/semmetodo.cs:line 3

O que quer dizer: não há método Dividir, então o GetMethod devolveu null, e chamar .Invoke em null quebrou. O compilador até avisou (CS8602): o GetMethod devolve MethodInfo?, que pode ser nulo. Como consertar: confira antes: if (metodo is null) { Console.WriteLine("Operação desconhecida"); return; }, ou use metodo?.Invoke(…). Não cale o aviso com ! sem ter certeza de que o método existe.

Número errado de argumentos: TargetParameterCountException

Este código tem um erro de propósito.
Calculadora calc = new();
var metodo = typeof(Calculadora).GetMethod("Somar")!;
Console.WriteLine(metodo.Invoke(calc, [6]));
class Calculadora
{
    public int Somar(int a, int b) => a + b;
}
$ dotnet run parametros.cs
Unhandled exception. System.Reflection.TargetParameterCountException: Parameter count mismatch.
   at System.Reflection.MethodBaseInvoker.ThrowTargetParameterCountException()
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Program.<Main>$(String[] args) in /home/ana/cs/parametros.cs:line 3

O que quer dizer: "a quantidade de parâmetros não bate". Somar pede dois números e recebeu um. Numa chamada normal, calc.Somar(6) seria o erro de compilação CS7036; com reflexão, o compilador não confere nada, e o erro só aparece ao rodar. Como consertar: passe todos os argumentos, na ordem e com os tipos certos (confira com metodo.GetParameters()).

A exceção "embrulhada": TargetInvocationException

Este código tem um erro de propósito.
Calculadora calc = new();
var metodo = typeof(Calculadora).GetMethod("Dividir")!;
Console.WriteLine(metodo.Invoke(calc, [6, 0]));
class Calculadora
{
    public int Dividir(int a, int b) => a / b;
}
$ dotnet run invocar.cs
Unhandled exception. System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.DivideByZeroException: Attempted to divide by zero.
   at Calculadora.Dividir(Int32 a, Int32 b) in /home/ana/cs/invocar.cs:line 6
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Program.<Main>$(String[] args) in /home/ana/cs/invocar.cs:line 3

O que quer dizer: "o alvo da chamada lançou uma exceção". O método Dividir rodou e dividiu por zero; o Invoke pegou essa DivideByZeroException e a embrulhou numa TargetInvocationException. A exceção de verdade é a de dentro, depois do --->: é ela que diz o problema e a linha (6). Como consertar: o problema está no método chamado, não na reflexão. Num try/catch, a exceção original fica em ex.InnerException.

No editor do guia, o rastro dessas duas exceções vem mais curto: as linhas at System.Reflection… de dentro do .NET não aparecem. O resto é igual: o tipo das exceções, as mensagens, a linha 6 do Calculadora.Dividir (na exceção de dentro) e a linha 3 do Program.<Main>$.


Referência

Para…UseDevolve
A ficha de um tipo conhecidotypeof(T)Type
A ficha do tipo real de um objetoobj.GetType()Type
Um tipo pelo nomeType.GetType("Ns.Nome")Type?
Nome, nome completo, baset.Name, t.FullName, t.BaseTypetextos / Type
Propriedades públicast.GetProperties()PropertyInfo[]
Ler / gravar uma propriedadep.GetValue(obj) / p.SetValue(obj, v)object?
Métodost.GetMethods(flags), t.GetMethod("Nome")MethodInfo[] / MethodInfo?
Chamarm.Invoke(obj, [args]) (null no lugar de obj para static)object?
Campo (inclusive privado)t.GetField("_x", BindingFlags.NonPublic | BindingFlags.Instance)FieldInfo?
Criar um objetoActivator.CreateInstance(t)object?
Atributox.GetCustomAttribute<A>()A?
FiltrosBindingFlags.Public, NonPublic, Instance, Static, DeclaredOnly—

Exercícios

1. Complete para obter a ficha do tipo de um objeto:

Type t = produto.;

2. Que método lista as propriedades públicas de um Type?

foreach (var p in t.)

3. Que método do MethodInfo chama o método?

metodo.(calc, [6, 7]);

4. object x = 42; Console.WriteLine(x.GetType().Name); mostra:

5. typeof(Calc).GetMethod("Somr") (com erro de digitação) faz o quê?

6. Um método chamado com Invoke lançou DivideByZeroException. Que exceção chega a quem chamou o Invoke?

Missão final: o validador já está pronto, mas o Fornecedor não tem etiquetas, então tudo passa. Cole [Obrigatorio] em Nome e em Email (o Telefone é opcional). Os dois fornecedores de teste devem dar Nome: obrigatório e Email: obrigatório, com 1 problema(s) cada.

using System.Reflection;

Validar(new Fornecedor { Nome = "", Email = "papelaria@email.com" });
Validar(new Fornecedor { Nome = "Gráfica Sol", Email = null });

void Validar(Fornecedor f)
{
    int problemas = 0;
    foreach (PropertyInfo p in typeof(Fornecedor).GetProperties())
    {
        object? valor = p.GetValue(f);
        if (p.GetCustomAttribute<ObrigatorioAttribute>() is not null && (valor is null || valor is ""))
        {
            Console.WriteLine($"{p.Name}: obrigatório");
            problemas++;
        }
    }
    Console.WriteLine(problemas == 0 ? "tudo certo" : $"{problemas} problema(s)");
}

[AttributeUsage(AttributeTargets.Property)]
class ObrigatorioAttribute : Attribute { }

class Fornecedor
{
    public string Nome { get; set; } = "";
    public string? Email { get; set; }
    public string? Telefone { get; set; }
}
using System.Reflection;

Validar(new Fornecedor { Nome = "", Email = "papelaria@email.com" });
Validar(new Fornecedor { Nome = "Gráfica Sol", Email = null });

void Validar(Fornecedor f)
{
    int problemas = 0;
    foreach (PropertyInfo p in typeof(Fornecedor).GetProperties())
    {
        object? valor = p.GetValue(f);
        if (p.GetCustomAttribute<ObrigatorioAttribute>() is not null && (valor is null || valor is ""))
        {
            Console.WriteLine($"{p.Name}: obrigatório");
            problemas++;
        }
    }
    Console.WriteLine(problemas == 0 ? "tudo certo" : $"{problemas} problema(s)");
}

[AttributeUsage(AttributeTargets.Property)]
class ObrigatorioAttribute : Attribute { }

class Fornecedor
{
    [Obrigatorio] public string Nome { get; set; } = "";
    [Obrigatorio] public string? Email { get; set; }
    public string? Telefone { get; set; }
}

A solução, na máquina da Ana:

$ dotnet run fornecedor.cs
Nome: obrigatório
1 problema(s)
Email: obrigatório
1 problema(s)

Resumo

  • Reflexão é o programa lendo, enquanto roda, as fichas (metadados) dos seus tipos. A ficha é um Type: typeof(T) ou obj.GetType(), que sempre conta o tipo real.
  • GetProperties() e GetValue(obj) percorrem as propriedades de qualquer objeto; GetMethod("Nome") e Invoke(obj, args) chamam um método pelo nome; Activator.CreateInstance cria objetos.
  • GetCustomAttribute<A>() lê atributos: é assim que validadores, serializadores e frameworks de teste funcionam.
  • Os erros de reflexão só aparecem ao rodar: null quando o nome não existe, TargetParameterCountException com argumentos errados, TargetInvocationException embrulhando a exceção do método chamado.
  • Reflexão é lenta, ignora o private e atrapalha programas enxugados (avisos IL2075/IL2026, que o dotnet run arquivo.cs e o editor do guia mostram). Use quando não houver outro jeito; se você conhece o tipo, use o tipo.

Próxima aula: Expressões Regulares (Regex), para procurar e validar padrões dentro de textos.