Métodos (Guia de Programação em C#)

Um método é um bloco de código que contém uma série de instruções. Um programa faz com que as instruções sejam executadas chamando o método e especificando os argumentos de método necessários. No C#, todas as instruções executadas são realizadas no contexto de um método.

O método Main é o ponto de entrada para todos os aplicativos C# e é chamado pelo CLR (Common Language Runtime) quando o programa é iniciado. Em um aplicativo que usa instruções de nível superior, o Main método é gerado pelo compilador e contém todas as instruções de nível superior.

Observação

Este artigo discute métodos nomeados. Para obter mais informações sobre funções anônimas, consulte Expressões lambda.

Assinaturas de método

Os métodos são declarados em uma classe, struct ou interface especificando o nível de acesso, como public ou private, modificadores opcionais, como abstract ou sealed, o valor retornado, o nome do método e os parâmetros de método. Juntas, essas partes são a assinatura do método.

Importante

Um tipo de retorno de um método não faz parte da assinatura do método para fins de sobrecarga de método. No entanto, ele faz parte da assinatura do método ao determinar a compatibilidade entre um delegado e o método para o qual ele aponta.

Os parâmetros de método estão entre parênteses e separados por vírgulas. Parênteses vazios indicam que o método não requer parâmetros. Essa classe contém quatro métodos:

abstract class Motorcycle
{
    // Anyone can call this.
    public void StartEngine() {/* Method statements here */ }

    // Only derived classes can call this.
    protected void AddGas(int gallons) { /* Method statements here */ }

    // Derived classes can override the base class implementation.
    public virtual int Drive(int miles, int speed) { /* Method statements here */ return 1; }

    // Derived classes must implement this.
    public abstract double GetTopSpeed();
}

Acesso a método

Chamar um método em um objeto é como acessar um campo. Após o nome do objeto, adicione um ponto final, o nome do método e parênteses. Os argumentos são listados dentro dos parênteses e são separados por vírgulas. Os métodos da classe Motorcycle podem, portanto, ser chamados como no exemplo a seguir:

class TestMotorcycle : Motorcycle
{
    public override double GetTopSpeed()
    {
        return 108.4;
    }

    static void Main()
    {
        TestMotorcycle moto = new TestMotorcycle();

        moto.StartEngine();
        moto.AddGas(15);
        moto.Drive(5, 20);
        double speed = moto.GetTopSpeed();
        Console.WriteLine("My top speed is {0}", speed);
    }
}

Parâmetros de método versus argumentos

A definição do método especifica os nomes e tipos de quaisquer parâmetros obrigatórios. Quando o código de chamada chama o método, ele fornece valores concretos, chamados argumentos, para cada parâmetro. Os argumentos devem ser compatíveis com o tipo de parâmetro, mas o nome do argumento (se algum) usado no código de chamada não precisa ser o mesmo que o parâmetro chamado definido no método. Por exemplo:

public void Caller()
{
    int numA = 4;
    // Call with an int variable.
    int productA = Square(numA);

    int numB = 32;
    // Call with another int variable.
    int productB = Square(numB);

    // Call with an integer literal.
    int productC = Square(12);

    // Call with an expression that evaluates to int.
    productC = Square(productA * 3);
}

int Square(int i)
{
    // Store input argument in a local variable.
    int input = i;
    return input * input;
}

Passando por referência versus passando por valor

Por padrão, quando uma instância de um tipo de valor é passada para um método, sua cópia é passada em vez da própria instância. Portanto, as alterações no argumento não têm nenhum efeito sobre a instância original no método de chamada. Para passar uma instância de tipo de valor por referência, use a palavra-chave ref. Para obter mais informações, consulte Passando parâmetros de tipo de valor.

Quando um objeto de tipo de referência é passado para um método, uma referência ao objeto é passada. Ou seja, o método recebe não o objeto em si, mas um argumento que indica o local do objeto. Se você alterar um membro do objeto usando essa referência, a alteração será refletida no argumento no método de chamada, ainda que você passe o objeto por valor.

Crie um tipo de referência usando a palavra-chave class, como mostra o exemplo a seguir:

public class SampleRefType
{
    public int value;
}

Agora, se você passar um objeto com base nesse tipo para um método, uma referência ao objeto será passada. O exemplo a seguir passa um objeto do tipo SampleRefType ao método ModifyObject:

public static void TestRefType()
{
    SampleRefType rt = new SampleRefType();
    rt.value = 44;
    ModifyObject(rt);
    Console.WriteLine(rt.value);
}

static void ModifyObject(SampleRefType obj)
{
    obj.value = 33;
}

O exemplo faz essencialmente a mesma coisa que o exemplo anterior, pois ele passa um argumento por valor para um método. No entanto, como um tipo de referência é usado, o resultado é diferente. A modificação feita em ModifyObject para o campo value do parâmetro, obj, também altera o campo value do argumento, rt, no método TestRefType. O método TestRefType exibe 33 como a saída.

Para obter mais informações sobre como passar tipos de referência por referência e por valor, consulte Passando parâmetros de tipo de referência e Tipos de referência.

Valores retornados

Os métodos podem retornar um valor para o chamador. Se o tipo de retorno (o tipo listado antes do nome do método) não for void, o método poderá retornar o valor usando a instrução return. Uma instrução com a palavra-chave return seguida por uma variável que corresponde ao tipo de retorno retornará esse valor ao chamador do método.

O valor pode ser retornado ao chamador por valor ou por referência. Valores são retornados ao chamador por referência se a ref palavra-chave é usada na assinatura do método e segue cada palavra-chave return. Por exemplo, a instrução de retorno e a assinatura de método a seguir indicam que o método retorna uma variável chamada estDistance por referência para o chamador.

public ref double GetEstimatedDistance()
{
    return ref estDistance;
}

A palavra-chave return também interrompe a execução do método. Se o tipo de retorno for void, uma instrução return sem um valor ainda será útil para interromper a execução do método. Sem a palavra-chave return, a execução do método será interrompida quando chegar ao final do bloco de código. Métodos com um tipo de retorno não nulo devem usar a palavra-chave return para retornar um valor. Por exemplo, esses dois métodos usam a palavra-chave return para retornar inteiros:

class SimpleMath
{
    public int AddTwoNumbers(int number1, int number2)
    {
        return number1 + number2;
    }

    public int SquareANumber(int number)
    {
        return number * number;
    }
}

Para usar um valor retornado de um método, o método de chamada pode usar a chamada de método em si em qualquer lugar que um valor do mesmo tipo seria suficiente. Você também pode atribuir o valor retornado a uma variável. Por exemplo, os dois exemplos de código a seguir obtêm a mesma meta:

int result = obj.AddTwoNumbers(1, 2);
result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);
result = obj.SquareANumber(obj.AddTwoNumbers(1, 2));
// The result is 9.
Console.WriteLine(result);

Usar uma variável local, nesse caso, result, para armazenar um valor é opcional. Isso pode ajudar a legibilidade do código ou pode ser necessário se você precisar armazenar o valor original do argumento para todo o escopo do método.

Para usar o valor retornado de um método por referência, você deve declarar uma variável ref local se você pretende modificar seu valor. Por exemplo, se o método Planet.GetEstimatedDistance retorna um valor Double por referência, você pode defini-lo como uma variável ref local com código semelhante ao seguinte:

ref double distance = ref Planet.GetEstimatedDistance();

Retornar uma matriz multidimensional de um método, M, que modifica o conteúdo da matriz, não é necessário se a função de chamada passou a matriz para M. Você pode retornar a matriz resultante de M para um bom estilo ou fluxo funcional de valores, mas isso não é necessário porque o C# passa todos os tipos de referência por valor e o valor de uma referência de matriz é o ponteiro para a matriz. No método M, as alterações do conteúdo da matriz podem ser observadas por qualquer código que tiver uma referência à matriz, conforme mostrado no exemplo a seguir:

static void Main(string[] args)
{
    int[,] matrix = new int[2, 2];
    FillMatrix(matrix);
    // matrix is now full of -1
}

public static void FillMatrix(int[,] matrix)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
    {
        for (int j = 0; j < matrix.GetLength(1); j++)
        {
            matrix[i, j] = -1;
        }
    }
}

Métodos assíncronos

Usando o recurso async, você pode invocar métodos assíncronos sem usar retornos de chamada explícitos ou dividir manualmente seu código entre vários métodos ou expressões lambda.

Se marcar um método com o modificador async, você poderá usar o operador await no método. Quando o controle atinge uma expressão await no método assíncrono, ele retorna para o chamador e o progresso no método é suspenso até a tarefa aguardada ser concluída. Quando a tarefa for concluída, a execução poderá ser retomada no método.

Observação

Um método assíncrono retorna para o chamador quando encontra o primeiro objeto esperado que ainda não está completo ou chega ao final do método assíncrono, o que ocorrer primeiro.

Um método assíncrono normalmente tem um tipo de retornoTask<TResult>, Task, IAsyncEnumerable<T> ou void. O tipo de retorno void é usado principalmente para definir manipuladores de eventos, nos quais o tipo de retorno void é necessário. Um método assíncrono que retorna void não pode ser aguardado e o chamador de um método de retorno nulo não pode capturar as exceções que esse método gera. Um método assíncrono pode ter qualquer tipo de retorno semelhante a tarefas.

No exemplo a seguir, DelayAsync é um método assíncrono que tem um tipo de retorno de Task<TResult>. DelayAsync tem uma instrução return que retorna um número inteiro. Portanto, a declaração do método de Task<int> deve ter um tipo de retorno de DelayAsync. Como o tipo de retorno é Task<int>, a avaliação da expressão await em DoSomethingAsync produz um inteiro, como a instrução a seguir demonstra: int result = await delayTask.

O método Main é um exemplo de método assíncrono que tem um tipo de retorno Task. Ele vai para o método DoSomethingAsync e, como é expresso com uma única linha, ele pode omitir as palavras-chave async e await. Como DoSomethingAsync é um método assíncrono, a tarefa para a chamada para DoSomethingAsync deve ser colocada em espera, como mostra a seguinte instrução: await DoSomethingAsync();.

class Program
{
    static Task Main() => DoSomethingAsync();

    static async Task DoSomethingAsync()
    {
        Task<int> delayTask = DelayAsync();
        int result = await delayTask;

        // The previous two statements may be combined into
        // the following statement.
        //int result = await DelayAsync();

        Console.WriteLine($"Result: {result}");
    }

    static async Task<int> DelayAsync()
    {
        await Task.Delay(100);
        return 5;
    }
}
// Example output:
//   Result: 5

Um método assíncrono não pode declarar nenhum parâmetro ref ou out, mas pode chamar métodos com tais parâmetros.

Para obter mais informações sobre os métodos assíncronos, consulte Programação assíncrona com async e await e Tipos de retorno Async.

Definições de corpo de expressão

É comum ter definições de método que simplesmente retornam imediatamente com o resultado de uma expressão ou que têm uma única instrução como o corpo do método. Há um atalho de sintaxe para definir esses métodos usando =>:

public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
public void Print() => Console.WriteLine(First + " " + Last);
// Works with operators, properties, and indexers too.
public static Complex operator +(Complex a, Complex b) => a.Add(b);
public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id);

Se o método retornar void ou for um método assíncrono, o corpo do método deverá ser uma expressão de instrução (igual às lambdas). Para propriedades e indexadores, eles devem ser somente leitura e você não usa a palavra-chave do acessador get.

Iterators

Um iterador realiza uma iteração personalizada em uma coleção, como uma lista ou uma matriz. Um iterador usa a instrução yield return para retornar um elemento de cada vez. Quando uma instrução yield return for atingida, o local atual no código será lembrado. A execução será reiniciada desse local quando o iterador for chamado na próxima vez.

Você chama um iterador de um código de cliente usando uma instrução foreach.

O tipo de retorno de um iterador pode ser IEnumerable, IEnumerable<T>, IAsyncEnumerable<T>, IEnumerator ou IEnumerator<T>.

Para obter mais informações, consulte Iteradores.

Especificação da linguagem C#

Para obter mais informações, consulte a Especificação da linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso de C#.

Confira também