← C#: linguagem e decisões de aplicação
03 / 10 · 50 MIN

Métodos, parâmetros e padrões

Segue os efeitos de uma chamada, distingue contratos de parâmetros e classifica entradas com regras explícitas.

O método recebe um valor ou uma variável?

Uma assinatura ajuda a perceber quais efeitos uma chamada pode ter. No caso habitual, sem modificador, o argumento é passado por valor. Com int queued = 6, chamar Adjust(queued) fornece ao parâmetro uma cópia do número. Somar dois ao parâmetro não altera queued. Para tornar visível um novo total, o método pode devolvê-lo e o chamador pode atribuí-lo à variável pretendida.

Com uma classe, o valor copiado é uma referência. Se Change(config) altera config.Limit, o chamador pode observar a alteração no mesmo objeto. Se o método faz config = new Settings(), apenas substitui a referência local. Desenha duas caixas para as variáveis e uma caixa para o objeto: mudar uma seta é diferente de mudar o objeto alcançado por ambas.

Este modelo permite rever uma rotina sem a executar. Identifica primeiro o que está a ser copiado, depois o que está a ser atribuído e, por fim, quem observa essa alteração. Uma chamada sem ref pode ter efeitos num objeto partilhado; ausência de ref não é uma garantia geral de pureza.

ref, out e in exprimem contratos diferentes

ref permite ao método usar e alterar uma variável que o chamador já inicializou. Na chamada Increment(ref count), a possibilidade de alteração da variável fica visível. out permite produzir uma saída sem exigir um valor anterior, mas todos os caminhos que regressam normalmente devem atribuir essa saída. Um return false continua a ser um regresso normal.

Num método TrySelect(bool available, out int selected), escrever if (!available) return false antes de atribuir selected impede a compilação. Pode atribuir-se um valor de saída nesse ramo e documentar que só deve ser consumido quando o resultado booleano é true. O número de saída isolado não substitui a decisão de sucesso.

in limita a reatribuição ou escrita através do parâmetro readonly. Não é uma promessa de ausência de efeitos em todo o programa. O compilador pode usar um temporário quando a chamada permite essa forma. Não acrescentes in apenas por supor uma melhoria de desempenho: o contrato deve continuar claro e qualquer ganho exige medição num contexto real.

Nomes associam valores; a escrita ordena a avaliação

Argumentos nomeados ajudam a ler chamadas com vários valores semelhantes. Archive(name: "batch", copies: 3) torna os papéis visíveis. Se ambos estão nomeados, podem aparecer numa ordem diferente da declaração. Um parâmetro opcional fornece um valor quando o argumento correspondente é omitido; não elimina a necessidade de fornecer os parâmetros obrigatórios.

A associação por nome não reorganiza a avaliação das expressões. Em Pair(second: ReadSecond(), first: ReadFirst()), ReadSecond é avaliado antes de ReadFirst. O resultado da segunda expressão é depois associado a first. Esta diferença interessa se os métodos leem um cursor, registam eventos ou alteram estado. Antes de decidir o resultado, desenha a sequência das avaliações e a associação aos parâmetros separadamente.

Evita depender de efeitos ocultos só para tornar uma chamada curta. Quando a ordem tem significado operacional, variáveis locais com nomes claros ajudam a mostrar os passos. Um leitor deve conseguir distinguir o momento em que um dado foi obtido do lugar onde é usado no método chamado.

Reconhecer um tipo não é converter um número

Um padrão de declaração, como value is int amount, verifica se o valor permite essa correspondência e disponibiliza amount no ramo em que ela se verifica. Se value é null, o padrão int não corresponde. Se contém um long boxed, também não corresponde a int, mesmo que o número seja pequeno. O padrão não faz parsing de texto nem conversão numérica de long para int.

Isto ajuda a construir fronteiras explícitas para entradas heterogéneas. Se uma interface aceita apenas int, um objeto com o valor 8L é de outro tipo e pode ser rejeitado. Se o contrato aceita ambos, escreve essa decisão e trata os limites da conversão separadamente. Não alteres o padrão apenas até um exemplo concreto produzir true.

Os padrões relacionais descrevem condições como > 20. Podem ser combinados numa classificação, mas a ordem dos braços e as guardas também contam. Mantém as expressões de decisão simples: uma classificação deve explicar qual regra se aplicou, em vez de esconder alterações de estado em condições difíceis de seguir.

switch: prioridade, guardas e valores desconhecidos

Uma expressão switch seleciona o primeiro braço cujo padrão corresponde e cuja guarda when, se existir, é true. Num serviço, > 20 when urgent pode preceder > 20. Um volume grande sem urgência falha a primeira guarda e segue para o braço seguinte. A existência do primeiro padrão não elimina automaticamente os restantes candidatos.

O descarte _ cobre qualquer valor que chegue a esse ponto, incluindo null. Colocá-lo primeiro e acrescentar depois um caso constante torna o caso posterior integralmente coberto; o compilador rejeita esse braço inalcançável. Não existe uma ordenação automática que dê prioridade ao padrão mais específico.

Cobrir todos os valores também é uma decisão de negócio. Usar _ => "accepted" pode compilar e, ainda assim, aceitar estados que nunca foram aprovados. Decide o resultado de null e de valores futuros de propósito. Retirar a cobertura e esperar uma exceção não cria automaticamente um registo de revisão nem uma resposta compreensível para o chamador.

Exemplo guiado: classificar antes de produzir efeitos

Uma aplicação recebe uma contagem representada como object?. O contrato deste exemplo só aceita int entre 1 e 500; null, outros tipos e valores fora do intervalo são rejeitados. Entre as entradas aceites, as urgentes vão para priority e as restantes para standard. Estes nomes representam decisões locais, não reservas já realizadas.

O método abaixo começa por reconhecer um int dentro do intervalo. Cada caminho de regresso atribui a variável amount e produz uma classificação. Depois da validação, a expressão switch escolhe a prioridade pelo booleano urgent. No ramo de rejeição, amount é zero por convenção explícita e o retorno é reject. Um chamador que execute uma reserva deve consumir ambas as saídas e só agir sobre priority ou standard. Não pode interpretar amount = 0 isoladamente como prova de entrada válida.

Por leitura: Classify(30, true, out var n) produz priority e n = 30. Com 30 e false, produz standard e n = 30. Com 30L, null, 0 ou 501, produz reject e n = 0. O literal 30L é rejeitado pelo tipo, apesar de o valor caber no intervalo. Nenhum destes resultados prova disponibilidade de stock ou autorização do utilizador.

Checklist de revisão de uma chamada

Começa pelo contrato: o método deve devolver uma decisão, alterar uma variável ou modificar um objeto existente? Depois segue os argumentos na ordem em que são avaliados. Associa os valores aos nomes ou posições dos parâmetros e verifica cada caminho de regresso. Uma explicação útil distingue erro de compilação, resultado normal e exceção possível.

Ao desenhar overloads, nomes de parâmetros diferentes não criam assinaturas diferentes. Um parâmetro por valor pode distinguir-se de ref, mas ref e out, por si só, não permitem declarar dois overloads concorrentes com o resto da assinatura igual. Mudar apenas o tipo de retorno também não resolve essa colisão. É preferível nomear operações de intenção diferente com clareza a criar uma API difícil de chamar corretamente.

Resumo: acompanha a variável e o objeto separadamente, atribui todas as saídas out, distingue a ordem de avaliação da associação de argumentos e torna os valores desconhecidos explícitos. As avaliações seguintes pedem decisões por leitura e seleção; não exigem compilar ou executar os excertos.

static string Classify(object? input, bool urgent, out int amount)
{
    if (input is not int n || n < 1 || n > 500)
    {
        amount = 0;
        return "reject";
    }
    amount = n;
    return urgent switch
    {
        true => "priority",
        false => "standard"
    };
}
NA PRÁTICA

Método numa classe com nullable ativado. Classifica sem executar reservas: 30/true é priority; 30/false é standard; null, 30L, 0 e 501 são reject.

Armadilhas comuns

Confundir reatribuição do parâmetro com ativação no chamador; omitir out no ramo de insucesso; supor conversão numérica num padrão; aceitar estados novos pelo descarte.

Tópicos relacionados: Referências, nullability e texto · Objetos, interfaces e records · Exceções, recursos e ficheiros

Leva esta ideia contigo

Uma assinatura descreve como entram e saem valores; a ordem e a cobertura das decisões determinam o que a chamada realmente significa.

Criar conta

Referência: Method parameters and modifiers · C# 14 / .NET 10; no preview features