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 quaisquer argumentos de método necessários. Em C#, cada instrução executada é executada no contexto de um método. O Main método é o ponto de entrada para cada aplicativo C# e é chamado pelo Common Language Runtime (CLR) quando o programa é iniciado.

Nota

Este tópico discute métodos nomeados. Para obter informações sobre funções anônimas, consulte Expressões do Lambda.

Assinaturas de método

Os métodos são declarados em , classrecordou struct especificando:

  • Um nível de acesso opcional, como public ou private. A predefinição é private.
  • Modificadores opcionais, como abstract ou sealed.
  • O valor de retorno, ou void se o método não tiver nenhum.
  • O nome do método.
  • Qualquer parâmetro de método. Os parâmetros do método são colocados 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 do método. No entanto, 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 Motorcycle classe inclui um método sobrecarregado, Drive. Dois métodos têm o mesmo nome, mas devem ser diferenciados por seus tipos de parâmetros.

Invocação do método

Os métodos podem ser de instância ou estáticos. Invocar um método de instância requer que você instancie um objeto e chame o método nesse objeto; Um método de instância opera nessa instância e em seus dados. Você invoca um método estático fazendo referência ao nome do tipo ao qual o método pertence; Os métodos estáticos não operam em dados de instância. Tentar chamar um método estático através de uma instância de objeto gera um erro de 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 estiver chamando um static método), adicione um ponto, o nome do método e parênteses. Os argumentos são listados entre parênteses e separados por vírgulas.

A definição do método especifica os nomes e tipos de quaisquer parâmetros que são necessá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 que o parâmetro nomeado definido no método. No exemplo a seguir, o Square método inclui um único parâmetro do tipo int chamado i. A primeira chamada de método passa ao Square método 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 usava argumentos posicionais; ele fornece argumentos na mesma ordem que os parâmetros do método. Os métodos da Motorcycle classe podem, portanto, ser chamados como no exemplo a seguir. A chamada para o Drive método, por exemplo, inclui dois argumentos que correspondem aos dois parâmetros na sintaxe do método. O primeiro torna-se o miles valor do parâmetro, o segundo o valor do speed parâmetro.

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 para o 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 TestMotorcycle.Drive método. 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 os argumentos nomeados quando os argumentos nomeados estiverem nas posições corretas. O exemplo a seguir invoca o TestMotorcycle.Drive método 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 explicitamente definidos em um tipo, um tipo herda membros definidos em suas classes base. Como todos os tipos no sistema de tipos gerenciados herdam direta ou indiretamente da Object classe, todos os tipos herdam seus membros, como Equals(Object), GetType()e ToString(). O exemplo a seguir define uma Person classe, instancia dois Person objetos e chama o Person.Equals método para determinar se os dois objetos são iguais. O Equals método, no entanto, não é definido na Person classe, é 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

Os tipos podem substituir membros herdados usando a override palavra-chave e fornecendo uma implementação para o método substituído. A assinatura do método deve ser a mesma do método substituído. O exemplo a seguir é como o anterior, exceto que ele substitui o Equals(Object) método. (Ele também substitui o GetHashCode() método, uma vez que os dois métodos se destinam 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

Parâmetros de passagem

Os tipos em C# são tipos de valor ou tipos de referência. Para obter uma lista de tipos de valores internos, consulte Tipos. Por padrão, os tipos de valor e os tipos de referência 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 no método chamado não têm efeito sobre o 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 seu valor para 20 e o passa para um método chamado ModifyValue que altera o valor da variável para 30. Quando o método retorna, no entanto, 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 de um 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 a localização do objeto. Se você alterar um membro do objeto usando essa referência, a alteração será refletida no objeto quando o controle retornar ao método de chamada. No entanto, a substituição do objeto passado para o método não tem efeito sobre o objeto original quando o controle retorna ao chamador.

O exemplo a seguir define uma classe (que é um tipo de referência) chamada SampleRefType. Ele instancia um SampleRefType objeto, atribui 44 ao seu value campo e passa o objeto para o ModifyObject método. 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 que é feita no ModifyObjectobj.value campo também altera o value campo do argumento, rtno Main método para 33, como mostra a saída do exemplo.

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;
}

Passagem de 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 ao método de chamada. Para passar um parâmetro por referência, use a ref palavra-chave ou out . Você também pode passar um valor por referência para evitar cópias, mas ainda assim impedir modificações usando a in palavra-chave.

O exemplo a seguir é idêntico ao anterior, exceto que o valor é passado por referência ao ModifyValue método. Quando o valor do parâmetro é modificado no ModifyValue método, 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 ref envolve a troca dos valores das variáveis. Você passa duas variáveis para um método por referência, e o método troca seu conteúdo. 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 alterar o valor da própria referência, em vez do valor de seus elementos ou campos individuais.

Matrizes de parâmetros

Às vezes, o requisito de que você especifique o número exato de argumentos para o seu método é restritivo. Usando a params palavra-chave para indicar que um parâmetro é uma matriz de parâmetros, você permite que seu método seja chamado com um número variável de argumentos. O parâmetro marcado com a params palavra-chave deve ser um tipo de matriz e 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 desejado de elementos.
  • 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 Main método ilustra todas as quatro maneiras de invocar o método. Os chamadores não são obrigados a fornecer argumentos para parâmetros que incluam o params modificador. 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 seus parâmetros são necessários ou que eles são opcionais. Por padrão, os parâmetros são necessá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 é chamado, se nenhum argumento for fornecido para um parâmetro opcional, o valor padrão será usado.

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

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

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

  • Uma expressão do formulário new ValType(), onde ValType é um tipo de valor. Isso invoca o construtor implícito sem parâmetros do tipo de valor, que não é um membro real do tipo.

    Nota

    Em C# 10 e posteriores, quando uma expressão do formulário new ValType() invoca o construtor sem parâmetros explicitamente definido 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 default(ValType) expressão ou o default literal para fornecer o valor do parâmetro padrão. 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 incluir parâmetros obrigatórios e opcionais, os parâmetros opcionais serão definidos no final da lista de parâmetros, após todos os parâmetros necessá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 ExampleMethod método, por exemplo, se o chamador fornece um argumento para o description parâmetro, ele também deve fornecer um para o optionalInt parâmetro. 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 de compilador "Argumento ausente".

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

O exemplo a seguir chama o ExampleMethod método 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 necessário, mas usa um argumento nomeado para fornecer um valor para o description parâmetro enquanto omite o optionalInt argumento.

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 como o compilador C# determina qual sobrecarga específica deve ser invocada por uma chamada de método, da seguinte maneira:

  • Um método, indexador ou construtor é um candidato para execução se cada um de seus parâmetros for opcional ou corresponder, por nome ou por 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 para conversões preferenciais serão aplicadas aos argumentos explicitamente especificados. Os argumentos omitidos para parâmetros opcionais são ignorados.
  • Se dois candidatos forem considerados igualmente bons, a preferência vai para um candidato que não tenha parâmetros opcionais para os quais os argumentos foram omitidos na chamada. Esta é uma consequência de uma preferência geral na resolução de sobrecarga para candidatos que têm menos parâmetros.

Valores de retorno

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 voidfor , o método pode retornar o valor usando a palavra-chave return . Uma instrução com a return palavra-chave seguida por uma variável, constante ou expressão que corresponda ao tipo de retorno retornará esse valor para o chamador do método. Os métodos com um tipo de retorno não vazio são necessários para usar a return palavra-chave para retornar um valor. A return palavra-chave também interrompe a execução do método.

Se o tipo de retorno for void, uma return instrução sem um valor ainda será útil para interromper a execução do método. Sem a palavra-chave return , o método irá parar de ser executado quando chegar ao final do bloco de código.

Por exemplo, esses dois métodos usam a return palavra-chave 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 própria chamada de método em qualquer lugar onde um valor do mesmo tipo seria suficiente. Você também pode atribuir o valor de retorno a uma variável. Por exemplo, os dois exemplos de código a seguir atingem o mesmo objetivo:

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, neste 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 GetPersonalInfo que é retornado pelo método. A expressão (per.FirstName, per.MiddleName, per.LastName, per.Age) é a tupla literal, o método retorna o nome, o meio e o sobrenome, juntamente com a idade, de um PersonInfo objeto.

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 um código como o seguinte:

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

Os nomes também podem ser atribuídos aos elementos de tupla na definição de tipo de tupla. O exemplo a seguir mostra uma versão alternativa do GetPersonalInfo método 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 GetPersonalInfo método pode então ser modificada da seguinte forma:

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

Se um método é passado uma matriz como um argumento e modifica o valor de elementos individuais, não é necessário que o método retorne a matriz, embora você possa optar por fazê-lo para um bom estilo ou fluxo funcional de valores. 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 values matriz que são feitas no DoubleValues método são observáveis por qualquer código que tenha uma 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:

  • Modifique o código-fonte desse tipo. Você não pode fazer isso, é claro, se você não possui o código-fonte do tipo. E isso se torna uma mudança significativa se você também adicionar quaisquer campos de dados privados para dar suporte ao método.
  • Defina o novo método em uma classe derivada. Um método não pode ser adicionado dessa maneira usando herança para outros tipos, como estruturas e enumerações. Também não pode ser usado para "adicionar" um método a uma classe selada.

Os métodos de extensão permitem "adicionar" um método a um tipo existente sem modificar o próprio tipo 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 que o 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 assíncrono, você pode invocar métodos assíncronos sem usar retornos de chamada explícitos ou dividir manualmente seu código em vários métodos ou expressões lambda.

Se você marcar um método com o modificador async , poderá usar o operador await no método. Quando o controle atinge uma await expressão no método assíncrono, o controle retorna ao chamador se a tarefa aguardada não for concluída e o progresso no método com a await palavra-chave é suspenso até que a tarefa esperada seja concluída. Quando a tarefa estiver concluída, a execução poderá ser retomada no método.

Nota

Um método assíncrono retorna ao chamador quando encontra o primeiro objeto aguardado 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 retorno de Task<TResult>, TaskIAsyncEnumerable<T>ou void. O void tipo de retorno é usado principalmente para definir manipuladores de eventos, onde um tipo de void retorno é necessário. Um método assíncrono que retorna void não pode ser aguardado, e o chamador de um método de retorno de vazio não pode capturar exceções que o método lança. Um método assíncrono pode ter qualquer tipo de retorno semelhante a uma tarefa.

No exemplo a seguir, DelayAsync é um método assíncrono que tem 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 de Task<int>. Como o tipo de retorno é Task<int>, a avaliação da await expressão em DoSomethingAsync produz um inteiro, como demonstra a instrução a seguir int result = await delayTask .

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

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

Membros com 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 retorna void ou é um método assíncrono, o corpo do método deve ser uma expressão de instrução (igual ao lambdas). Para propriedades e indexadores, eles devem ser somente leitura e você não usa a get palavra-chave accessor.

Iteradores

Um iterador executa uma iteração personalizada sobre uma coleção, como uma lista ou uma matriz. Um iterador usa a instrução yield return para retornar cada elemento, um de cada vez. Quando uma yield return instrução é alcançada, o local atual é 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.

Consulte também