Métodos 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.

Observação

Este tópico aborda os 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 class, record ou struct especificando:

  • Um nível de acesso opcional, como public ou private. O padrão é private.
  • Modificadores opcionais como abstract ou sealed.
  • O valor retornado ou void se o método não tiver nenhum.
  • O nome do método.
  • Quaisquer parâmetros de método. 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.

Essas partes juntas formam 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.

O exemplo a seguir define uma classe chamada Motorcycle que contém cinco métodos:

namespace MotorCycleExample
{
    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 can override the base class implementation.
        public virtual int Drive(TimeSpan time, int speed) { /* Method statements here */ return 0; }

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

A classe Motorcycle inclui um método sobrecarregado, Drive. Dois métodos têm o mesmo nome, mas devem ser diferenciados por seus tipos de parâmetro.

Invocação de método

Os métodos podem ser de instância ou estáticos. Invocar um método de instância requer que você crie uma instância de um objeto e chame o método nesse objeto. Um método de instância opera nessa instância e seus dados. Você invoca um método estático referenciando o nome do tipo ao qual o método pertence; os métodos estáticos não operam nos dados da instância. Tentar chamar um método estático por meio de uma instância do objeto gera um erro do compilador.

Chamar um método é como acessar um campo. Após o nome do objeto (se você estiver chamando um método de instância) ou o nome do tipo (se você estiver chamando um método static), adicione um ponto, o nome do método e parênteses. Os argumentos são listados dentro dos parênteses e são separados por vírgulas.

A definição do método especifica os nomes e tipos de quaisquer parâmetros obrigatórios. Quando um chamador invoca 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 for usado no código de chamada, não precisa ser o mesmo do parâmetro nomeado definido no método. No exemplo a seguir, o método Square inclui um único parâmetro do tipo int chamado i. A primeira chamada do método passa para o método Square uma variável do tipo int chamada num, a segunda, uma constante numérica e a terceira, uma expressão.

public static class SquareExample
{
    public static void Main()
    {
        // Call with an int variable.
        int num = 4;
        int productA = Square(num);

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

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

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

A forma mais comum de invocação de método usa argumentos posicionais, ela fornece os argumentos na mesma ordem que os parâmetros de método. Os métodos da classe Motorcycle, podem, portanto, ser chamados como no exemplo a seguir. A chamada para o método Drive, por exemplo, inclui dois argumentos que correspondem aos dois parâmetros na sintaxe do método. O primeiro se torna o valor do parâmetro miles, o segundo o valor do parâmetro speed.

class TestMotorcycle : Motorcycle
{
    public override double GetTopSpeed() => 108.4;

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

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

Você também pode usar argumentos nomeados em vez de argumentos posicionais ao invocar um método. Ao usar argumentos nomeados, você especifica o nome do parâmetro seguido por dois pontos (":") e o argumento. Os argumentos do método podem aparecer em qualquer ordem, desde que todos os argumentos necessários estejam presentes. O exemplo a seguir usa argumentos nomeados para invocar o método TestMotorcycle.Drive. Neste exemplo, os argumentos nomeados são passados na ordem oposta da lista de parâmetros do método.

namespace NamedMotorCycle;

class TestMotorcycle : Motorcycle
{
    public override int Drive(int miles, int speed) =>
        (int)Math.Round((double)miles / speed, 0);

    public override double GetTopSpeed() => 108.4;

    static void Main()
    {
        var moto = new TestMotorcycle();
        moto.StartEngine();
        moto.AddGas(15);
        int travelTime = moto.Drive(miles: 170, speed: 60);
        Console.WriteLine("Travel time: approx. {0} hours", travelTime);
    }
}
// The example displays the following output:
//      Travel time: approx. 3 hours

Você pode invocar um método usando argumentos posicionais e argumentos nomeados. No entanto, os argumentos posicionais só podem seguir argumentos nomeados quando os argumentos nomeados estiverem nas posições corretas. O exemplo a seguir invoca o método TestMotorcycle.Drive do exemplo anterior usando um argumento posicional e um argumento nomeado.

int travelTime = moto.Drive(170, speed: 55);

Métodos herdados e substituídos

Além dos membros que são definidos explicitamente em um tipo, um tipo herda membros definidos em suas classes base. Como todos os tipos no sistema de tipos gerenciado são herdados direta ou indiretamente da classe Object, todos os tipos herdam seus membros, como Equals(Object), GetType() e ToString(). O exemplo a seguir define uma classe Person, instancia dois objetos Person e chama o método Person.Equals para determinar se os dois objetos são iguais. O método Equals, no entanto, não é definido na classe Person; ele é herdado do Object.

public class Person
{
    public string FirstName = default!;
}

public static class ClassTypeExample
{
    public static void Main()
    {
        Person p1 = new() { FirstName = "John" };
        Person p2 = new() { FirstName = "John" };
        Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
    }
}
// The example displays the following output:
//      p1 = p2: False

Tipos podem substituir membros herdados usando a palavra-chave override e fornecendo uma implementação para o método substituído. A assinatura do método precisa ser igual à do método substituído. O exemplo a seguir é semelhante ao anterior, exceto que ele substitui o método Equals(Object). (Ele também substitui o método GetHashCode(), uma vez que os dois métodos destinam-se a fornecer resultados consistentes.)

namespace methods;

public class Person
{
    public string FirstName = default!;

    public override bool Equals(object? obj) =>
        obj is Person p2 &&
        FirstName.Equals(p2.FirstName);

    public override int GetHashCode() => FirstName.GetHashCode();
}

public static class Example
{
    public static void Main()
    {
        Person p1 = new() { FirstName = "John" };
        Person p2 = new() { FirstName = "John" };
        Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
    }
}
// The example displays the following output:
//      p1 = p2: True

Passando parâmetros

Os tipos no C# são tipos de valor ou tipos de referência. Para obter uma lista de tipos de valor internos, consulte Tipos. Por padrão, os tipos de referência e tipos de valor são passados para um método por valor.

Passando parâmetros por valor

Quando um tipo de valor é passado para um método por valor, uma cópia do objeto, em vez do próprio objeto, é passada para o método. Portanto, as alterações no objeto do método chamado não têm efeito no objeto original quando o controle retorna ao chamador.

O exemplo a seguir passa um tipo de valor para um método por valor e o método chamado tenta alterar o valor do tipo de valor. Ele define uma variável do tipo int, que é um tipo de valor, inicializa o valor para 20 e o passa para um método chamado ModifyValue que altera o valor da variável para 30. No entanto, quando o método retorna, o valor da variável permanece inalterado.

public static class ByValueExample
{
    public static void Main()
    {
        var value = 20;
        Console.WriteLine("In Main, value = {0}", value);
        ModifyValue(value);
        Console.WriteLine("Back in Main, value = {0}", value);
    }

    static void ModifyValue(int i)
    {
        i = 30;
        Console.WriteLine("In ModifyValue, parameter value = {0}", i);
        return;
    }
}
// The example displays the following output:
//      In Main, value = 20
//      In ModifyValue, parameter value = 30
//      Back in Main, value = 20

Quando um objeto do tipo de referência é passado para um método por valor, uma referência ao objeto é passada por valor. 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 objeto quando o controle retornar para o método de chamada. No entanto, substituir o objeto passado para o método não tem efeito no objeto original quando o controle retorna para o chamador.

O exemplo a seguir define uma classe (que é um tipo de referência) chamada SampleRefType. Ele cria uma instância de um objeto SampleRefType, atribui 44 ao seu campo value e passa o objeto para o método ModifyObject. Este exemplo faz essencialmente a mesma coisa que o exemplo anterior: ele passa um argumento por valor para um método. Mas como um tipo de referência é usado, o resultado é diferente. A modificação feita em ModifyObject para o campo obj.value também muda o campo value do argumento, rt, no método Main para 33, como a saída do exemplo mostra.

public class SampleRefType
{
    public int value;
}

public static class ByRefTypeExample
{
    public static void Main()
    {
        var rt = new SampleRefType { value = 44 };
        ModifyObject(rt);
        Console.WriteLine(rt.value);
    }

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

Passando parâmetros por referência

Você passa um parâmetro por referência quando deseja alterar o valor de um argumento em um método e deseja refletir essa alteração quando o controle retorna para o método de chamada. Para passar um parâmetro por referência, use a palavra-chave ref ou out. Você também pode passar um valor por referência para evitar a cópia e ainda evitar modificações usando a palavra-chave in.

O exemplo a seguir é idêntico ao anterior, exceto que o valor é passado por referência para o método ModifyValue. Quando o valor do parâmetro é modificado no método ModifyValue, a alteração no valor é refletida quando o controle retorna ao chamador.

public static class ByRefExample
{
    public static void Main()
    {
        var value = 20;
        Console.WriteLine("In Main, value = {0}", value);
        ModifyValue(ref value);
        Console.WriteLine("Back in Main, value = {0}", value);
    }

    private static void ModifyValue(ref int i)
    {
        i = 30;
        Console.WriteLine("In ModifyValue, parameter value = {0}", i);
        return;
    }
}
// The example displays the following output:
//      In Main, value = 20
//      In ModifyValue, parameter value = 30
//      Back in Main, value = 30

Um padrão comum que usa parâmetros pela referência envolve a troca os valores das variáveis. Você passa duas variáveis para um método por referência e o método troca seus conteúdos. O exemplo a seguir troca valores inteiros.


public static class RefSwapExample
{
    static void Main()
    {
        int i = 2, j = 3;
        Console.WriteLine("i = {0}  j = {1}", i, j);

        Swap(ref i, ref j);

        Console.WriteLine("i = {0}  j = {1}", i, j);
    }

    static void Swap(ref int x, ref int y) =>
        (y, x) = (x, y);
}
// The example displays the following output:
//      i = 2  j = 3
//      i = 3  j = 2

Passar um parâmetro de tipo de referência permite que você altere o valor da própria referência, em vez de o valor de seus campos ou elementos individuais.

Matrizes de parâmetros

Às vezes, o requisito de que você especifique o número exato de argumentos para o método é restritivo. Usando a palavra-chave params para indicar que um parâmetro é uma matriz de parâmetros, você permite que o método seja chamado com um número variável de argumentos. O parâmetro marcado com a palavra-chave params deve ser um tipo de matriz e ele deve ser o último parâmetro na lista de parâmetros do método.

Um chamador pode, então, invocar o método de uma das quatro maneiras:

  • Passando uma matriz do tipo apropriado que contém o número de elementos desejado.
  • Passando uma lista separada por vírgulas de argumentos individuais do tipo apropriado para o método.
  • Passando null.
  • Não fornecendo um argumento para a matriz de parâmetros.

O exemplo a seguir define um método chamado GetVowels que retorna todas as vogais de uma matriz de parâmetros. O método Main ilustra todas as quatro maneiras de invocar o método. Os chamadores não precisam fornecer argumentos para parâmetros que incluem o modificador params. Nesse caso, o parâmetro é uma matriz vazia.

static class ParamsExample
{
    static void Main()
    {
        string fromArray = GetVowels(["apple", "banana", "pear"]);
        Console.WriteLine($"Vowels from array: '{fromArray}'");

        string fromMultipleArguments = GetVowels("apple", "banana", "pear");
        Console.WriteLine($"Vowels from multiple arguments: '{fromMultipleArguments}'");

        string fromNull = GetVowels(null);
        Console.WriteLine($"Vowels from null: '{fromNull}'");

        string fromNoValue = GetVowels();
        Console.WriteLine($"Vowels from no value: '{fromNoValue}'");
    }

    static string GetVowels(params string[]? input)
    {
        if (input == null || input.Length == 0)
        {
            return string.Empty;
        }

        char[] vowels = ['A', 'E', 'I', 'O', 'U'];
        return string.Concat(
            input.SelectMany(
                word => word.Where(letter => vowels.Contains(char.ToUpper(letter)))));
    }
}

// The example displays the following output:
//     Vowels from array: 'aeaaaea'
//     Vowels from multiple arguments: 'aeaaaea'
//     Vowels from null: ''
//     Vowels from no value: ''

Parâmetros e argumentos opcionais

Uma definição de método pode especificar que os parâmetros são obrigatórios ou que são opcionais. Por padrão, os parâmetros são obrigatórios. Os parâmetros opcionais são especificados incluindo o valor padrão do parâmetro na definição do método. Quando o método for chamado, se nenhum argumento for fornecido para um parâmetro opcional, o valor padrão será usado em vez disso.

O valor padrão do parâmetro deve ser atribuído por um dos tipos de expressões a seguir:

  • Uma constante, como um número ou uma cadeia de caracteres literal.

  • Uma expressão do formulário default(SomeType), em que SomeType pode ser um tipo de valor ou um tipo de referência. Se for um tipo de referência, ele será efetivamente o mesmo que especificar null. Você pode usar o literal default, pois o compilador pode inferir o tipo a partir da declaração do parâmetro.

  • Uma expressão da forma new ValType(), em que ValType é um tipo de valor. Ela invoca o construtor sem parâmetros implícito do tipo de valor, que não é de fato um membro do tipo.

    Observação

    No C# 10 e posterior, quando uma expressão do formulário new ValType() invoca o construtor sem parâmetros definido explicitamente de um tipo de valor, o compilador gera um erro, pois o valor do parâmetro padrão deve ser uma constante de tempo de compilação. Use a expressão default(ValType) ou o literal default para fornecer o valor padrão do parâmetro. Para obter mais informações sobre construtores sem parâmetros, consulte a seção Inicialização de struct e valores padrão do artigo Tipos de estrutura.

Se um método inclui parâmetros obrigatórios e opcionais, os parâmetros opcionais são definidos no final da lista de parâmetros, após todos os parâmetros obrigatórios.

O exemplo a seguir define um método, ExampleMethod, que tem um parâmetro obrigatório e dois opcionais.

public class Options
{
    public void ExampleMethod(int required, int optionalInt = default,
                              string? description = default)
    {
        var msg = $"{description ?? "N/A"}: {required} + {optionalInt} = {required + optionalInt}";
        Console.WriteLine(msg);
    }
}

Se um método com vários argumentos opcionais for invocado usando argumentos posicionais, o chamador deverá fornecer um argumento para todos os parâmetros opcionais do primeiro ao último para o qual um argumento é fornecido. No caso do método ExampleMethod, por exemplo, se o chamador fornecer um argumento para o parâmetro description, ele deverá fornecer também um para o parâmetro optionalInt. opt.ExampleMethod(2, 2, "Addition of 2 and 2"); é uma chamada de método válida, opt.ExampleMethod(2, , "Addition of 2 and 0"); gera um erro do compilador de “Argumento ausente”.

Se um método for chamado usando argumentos nomeados ou uma combinação de argumentos posicionais e nomeados, o chamador poderá omitir todos os argumentos após o último argumento posicional na chamada do método.

A exemplo a seguir chama o método ExampleMethod três vezes. As duas primeiras chamadas de método usam argumentos posicionais. O primeiro omite ambos os argumentos opcionais, enquanto o segundo omite o último argumento. A terceira chamada de método fornece um argumento posicional para o parâmetro obrigatório, mas usa um argumento nomeado para fornecer um valor para o parâmetro description enquanto omite o argumento optionalInt.

public static class OptionsExample
{
    public static void Main()
    {
        var opt = new Options();
        opt.ExampleMethod(10);
        opt.ExampleMethod(10, 2);
        opt.ExampleMethod(12, description: "Addition with zero:");
    }
}
// The example displays the following output:
//      N/A: 10 + 0 = 10
//      N/A: 10 + 2 = 12
//      Addition with zero:: 12 + 0 = 12

O uso de parâmetros opcionais afeta a resolução de sobrecarga ou a maneira em que o compilador C# determina qual sobrecarga específica deve ser invocada pela invocada de método, da seguinte maneira:

  • Um método, indexador ou construtor é um candidato para a execução se cada um dos parâmetros é opcional ou corresponde, por nome ou posição, a um único argumento na instrução de chamada e esse argumento pode ser convertido para o tipo do parâmetro.
  • Se mais de um candidato for encontrado, as regras de resolução de sobrecarga de conversões preferenciais serão aplicadas aos argumentos que são especificados explicitamente. Os argumentos omitidos para parâmetros opcionais são ignorados.
  • Se dois candidatos são considerados igualmente bons, a preferência vai para um candidato que não tenha parâmetros opcionais para os quais argumentos foram omitidos na chamada. Esta é uma consequência da preferência geral na resolução de sobrecarga de candidatos que têm menos parâmetros.

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 palavra-chave return. Uma instrução com a palavra-chave return seguida por uma variável, constante ou expressão que corresponde ao tipo de retorno retornará esse valor para o chamador do método. Métodos com um tipo de retorno não nulo devem usar a palavra-chave return para retornar um valor. 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.

Por exemplo, esses dois métodos usam a palavra-chave return para retornar inteiros:

class SimpleMath
{
    public int AddTwoNumbers(int number1, int number2) =>
        number1 + number2;

    public int SquareANumber(int number) =>
        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.

Às vezes, você deseja que seu método retorne mais de um único valor. Você pode fazer isso facilmente usando tipos de tupla e literais de tupla. O tipo de tupla define os tipos de dados dos elementos da tupla. Os literais de tupla fornecem os valores reais da tupla retornada. No exemplo a seguir, (string, string, string, int) define o tipo de tupla que é retornado pelo método GetPersonalInfo. A expressão (per.FirstName, per.MiddleName, per.LastName, per.Age) é a tupla literal, o método retorna o nome, o nome do meio e o sobrenome, juntamente com a idade, de um objeto PersonInfo.

public (string, string, string, int) GetPersonalInfo(string id)
{
    PersonInfo per = PersonInfo.RetrieveInfoById(id);
    return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}

O chamador pode então consumir a tupla retornada com o código semelhante ao seguinte:

var person = GetPersonalInfo("111111111");
Console.WriteLine($"{person.Item1} {person.Item3}: age = {person.Item4}");

Os nomes também podem ser atribuídos aos elementos da tupla na definição de tipo de tupla. O exemplo a seguir mostra uma versão alternativa do método GetPersonalInfo que usa elementos nomeados:

public (string FName, string MName, string LName, int Age) GetPersonalInfo(string id)
{
    PersonInfo per = PersonInfo.RetrieveInfoById(id);
    return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}

A chamada anterior para o método GetPersonalInfo pode ser modificada da seguinte maneira:

var person = GetPersonalInfo("111111111");
Console.WriteLine($"{person.FName} {person.LName}: age = {person.Age}");

Se um método passa uma matriz como um argumento e modifica o valor de elementos individuais, o método não precisa retornar a matriz, embora você possa optar por fazer isso para obter um bom estilo ou um fluxo de valores funcional. Isso ocorre 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 exemplo a seguir, as alterações no conteúdo da matriz values realizados pelo método DoubleValues são observáveis por qualquer código que faz referência à matriz.


public static class ArrayValueExample
{
    static void Main()
    {
        int[] values = [2, 4, 6, 8];
        DoubleValues(values);
        foreach (var value in values)
        {
            Console.Write("{0}  ", value);
        }
    }

    public static void DoubleValues(int[] arr)
    {
        for (var ctr = 0; ctr <= arr.GetUpperBound(0); ctr++)
        {
            arr[ctr] *= 2;
        }
    }
}
// The example displays the following output:
//       4  8  12  16

Métodos de extensão

Normalmente, há duas maneiras de adicionar um método a um tipo existente:

  • Modificar o código-fonte para esse tipo. Você não pode fazer isso, é claro, se não tiver o código-fonte do tipo. E isso se torna uma alteração significativa se você também adicionar campos de dados privados para dar suporte ao método.
  • Definir o novo método em uma classe derivada. Não é possível adicionar um método dessa forma usando a herança para outros tipos, como estruturas e enumerações. Isso também não pode ser usado para “adicionar” um método a uma classe selada.

Os métodos de extensão permitem que você “adicione” um método a um tipo existente sem modificar o tipo em si ou implementar o novo método em um tipo herdado. O método de extensão também não precisa residir no mesmo assembly do tipo que ele estende. Você chama um método de extensão como se fosse um membro definido de um tipo.

Para obter mais informações, consulte Métodos de extensão.

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 atingir uma expressão await no método assíncrono, o controle retornará para o chamador se a tarefa aguardada não estiver concluída, e o progresso no método com a palavra-chave await será 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 como os de tarefa.

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

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 os parâmetros in, ref nem out, mas pode chamar métodos que tenham esses 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.

Membros aptos para 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 aos 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 será lembrado para que o chamador possa solicitar o próximo elemento na sequência.

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.

Confira também