Metodi in C#

Un metodo è un blocco di codice che contiene una serie di istruzioni. Un programma fa in modo che le istruzioni vengano eseguite chiamando il metodo e specificando gli argomenti del metodo obbligatori. In C#, ogni istruzione eseguita viene attuata nel contesto di un metodo. Il metodo Main è il punto di ingresso per ogni applicazione C# e viene chiamato da Common Language Runtime (CLR) quando viene avviato il programma.

Nota

In questo argomento vengono descritti i metodi denominati. Per informazioni sulle funzioni anonime, consultare Espressioni lambda.

Firme del metodo

I metodi vengono dichiarati in class, record o struct specificando:

  • Un livello di accesso facoltativo, ad esempio public o private. Il valore predefinito è private.
  • Modificatori facoltativi, ad esempio abstract o sealed.
  • Il valore restituito o void se il metodo non ha alcun valore.
  • Nome del metodo.
  • Tutti i parametri del metodo. I parametri del metodo vengono racchiusi tra parentesi e separati da virgole. Le parentesi vuote indicano che il metodo non richiede parametri.

Queste parti costituiscono la firma del metodo.

Importante

Un tipo restituito di un metodo non fa parte della firma del metodo in caso di overload dei metodi. Fa tuttavia parte della firma del metodo quando si determina la compatibilità tra un delegato e il metodo a cui fa riferimento.

L'esempio seguente definisce una classe denominata Motorcycle che contiene cinque metodi:

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

La classe Motorcycle include un metodo sottoposto a overload, ovvero Drive. Due metodi hanno lo stesso nome, ma devono essere differenziati in base ai relativi tipi di parametri.

Chiamata del metodo

I metodi possono essere di istanza o statici. Per chiamare un metodo di istanza è necessario creare un'istanza di un oggetto e chiamare il metodo nell'oggetto; un metodo di istanza agisce sull'istanza e i relativi dati. La chiamata a un metodo statico viene eseguita facendo riferimento al nome del tipo a cui appartiene il metodo; i metodi statici non agiscono sui dati dell'istanza. Se si tenta di chiamare un metodo statico attraverso un'istanza di un oggetto viene generato un errore del compilatore.

La chiamata a un metodo è un'operazione analoga all'accesso a un campo. Dopo il nome dell'oggetto (in una chiamata a un metodo di istanza) o il nome del tipo (in una chiamata a un metodo static), aggiungere un punto, il nome del metodo e le parentesi. Gli argomenti vengono elencati tra parentesi e separati da virgole.

La definizione del metodo specifica i nomi e i tipi di tutti i parametri obbligatori. Quando il chiamante chiama il metodo, specifica valori concreti, chiamati argomenti, per ogni parametro. Gli argomenti devono essere compatibili con il tipo di parametro, ma il nome dell'argomento, se usato nel codice chiamante, non deve essere lo stesso del parametro denominato definito nel metodo. Nell'esempio seguente il metodo Square include un singolo parametro di tipo int denominato i. La prima chiamata al metodo passa al metodo Square una variabile di tipo int denominata num, la seconda passa una costante numerica e la terza passa un'espressione.

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

La forma più comune di chiamata al metodo usa argomenti posizionali; specifica gli argomenti nello stesso ordine dei parametri del metodo. Per questa ragione, i metodi della classe Motorcycle possono essere chiamati come nell'esempio seguente. La chiamata al metodo Drive, ad esempio, include due argomenti che corrispondono ai due parametri nella sintassi del metodo. Il primo diventa il valore del parametro miles, il secondo il valore del parametro 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);
    }
}

Quando si esegue una chiamata a un metodo è anche possibile usare argomenti denominati anziché argomenti posizionali. Quando si usano gli argomenti denominati, si specifica il nome del parametro seguito da due punti (":") e l'argomento. Gli argomenti del metodo possono essere in qualsiasi ordine, a condizione che siano presenti tutti gli argomenti necessari. L'esempio seguente usa gli argomenti denominati per richiamare il metodo TestMotorcycle.Drive. In questo esempio gli argomenti denominati vengono passati nell'ordine inverso dall'elenco di parametri del metodo.

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

È possibile richiamare un metodo usando argomenti posizionali e denominati. Tuttavia, gli argomenti posizionali possono seguire solo gli argomenti denominati quando gli argomenti denominati si trovano nelle posizioni corrette. L'esempio seguente richiama il metodo TestMotorcycle.Drive dall'esempio precedente usando un argomento posizionale e un argomento denominato.

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

Metodi ereditati e sottoposti a override

Oltre ai membri definiti in modo esplicito in un tipo, un tipo eredita i membri definiti nelle relative classi base. Poiché tutti i tipi del sistema di tipi gestiti ereditano direttamente o indirettamente dalla classe Object, tutti i tipi di ereditano i relativi membri, ad esempio Equals(Object), GetType() e ToString(). L'esempio seguente definisce una classe Person, crea l'istanza di due oggetti Person e chiama il metodo Person.Equals per determinare se i due oggetti sono uguali. Il metodo Equals, tuttavia, non viene definito nella classe Person; viene ereditato da 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

I tipi possono eseguire l'override dei membri ereditati usando la parola chiave override e specificando un'implementazione per il metodo sottoposto a override. La firma del metodo deve essere la stessa del metodo sottoposto a override. L'esempio seguente è simile a quello precedente, ad eccezione del fatto che viene eseguito l'override del metodo Equals(Object). Viene anche eseguito l'override del metodo GetHashCode() poiché i due metodi devono fornire risultati coerenti.

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

Passaggio dei parametri

I tipi in C# sono tipi di valore o tipi di riferimento. Per un elenco dei tipi predefiniti, consultare Tipi. Per impostazione predefinita, i tipi di valore e i tipi di riferimento vengono passati a un metodo per valore.

Passaggio di parametri per valore

Quando un tipo valore viene passato a un metodo per valore, viene passata al metodo una copia dell'oggetto anziché l'oggetto stesso. Di conseguenza, le modifiche all'oggetto nel metodo chiamato non hanno effetto sull'oggetto originale quando il controllo torna al chiamante.

L'esempio seguente passa un tipo di valore a un metodo per valore e il metodo chiamato tenta di modificare il valore del tipo di valore. Definisce una variabile di tipo int, che è un tipo di valore, ne inizializza il valore a 20 e la passa a un metodo denominato ModifyValue che modifica il valore della variabile in 30. Quando il metodo viene restituito, tuttavia, il valore della variabile rimane invariato.

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 viene passato un oggetto di tipo riferimento a un metodo per valore, viene passato un riferimento all'oggetto per valore, ovvero il metodo riceve un argomento che indica la posizione dell'oggetto, ma non l'oggetto stesso. Se si modifica un membro dell'oggetto usando questo riferimento, la modifica si riflette nell'oggetto quando il controllo torna al metodo chiamante. Tuttavia, la sostituzione dell'oggetto passato al metodo non ha effetto sull'oggetto originale quando il controllo torna al chiamante.

L'esempio seguente definisce una classe (che è un tipo riferimento) denominata SampleRefType. Crea un'istanza di un oggetto SampleRefType, assegna 44 al campo value e passa l'oggetto al metodo ModifyObject. L'esempio è sostanzialmente uguale al precedente in quanto passa un argomento per valore a un metodo. ma, essendo usato un tipo riferimento, il risultato è diverso. La modifica eseguita in ModifyObject al campo obj.value cambia anche il campo value dell'argomento rt nel metodo Main in 33, come mostra l'output dell'esempio.

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

Passaggio di parametri per riferimento

È necessario passare un parametro per riferimento quando si vuole modificare il valore di un argomento in un metodo e questa modifica deve essere applicata quando il controllo torna al metodo chiamante. Per passare un parametro per riferimento, usare la parola chiave ref o out. È anche possibile passare un valore per riferimento per evitare la copia, ma comunque impedire modifiche usando la parola chiave in.

L'esempio seguente è identico a quello precedente, ad eccezione del fatto che il valore viene passato per riferimento al metodo ModifyValue. Quando il valore del parametro è modificato nel metodo ModifyValue, la modifica del valore si riflette quando il controllo torna al chiamante.

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

Un modello comune che usa parametri di riferimento implica lo scambio dei valori delle variabili. Si passano due variabili a un metodo per riferimento e il metodo scambia il loro contenuto. L'esempio seguente scambia i valori integer.


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

Il passaggio di un parametro di tipo riferimento consente di modificare il valore del riferimento stesso anziché il valore dei singoli elementi o campi.

Matrici di parametri

In alcuni casi, il requisito che richiede di specificare il numero esatto di argomenti per il metodo è limitativo. Usando la parola chiave params per indicare che un parametro è una matrice di parametri, si consente la chiamata al metodo con un numero variabile di argomenti. Il parametro contrassegnato con la parola chiave params deve essere di tipo matrice e deve essere l'ultimo parametro dell'elenco di parametri del metodo.

Un chiamante potrà quindi richiamare il metodo in uno dei quattro modi seguenti:

  • Passando una matrice del tipo appropriato che contiene il numero desiderato di elementi.
  • Passando un elenco delimitato da virgole di singoli argomenti del tipo appropriato al metodo.
  • Mediante il passaggio null.
  • Non specificando un argomento nella matrice di parametri.

L'esempio seguente definisce un metodo denominato GetVowels che restituisce tutte le vocali da una matrice di parametri. Il metodo Main illustra tutti e quattro i metodi di chiamata al metodo. Non è necessario che i chiamanti specifichino un argomento per i parametri che includono il modificatore params. In tal caso, il parametro è una matrice vuota.

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: ''

Parametri e argomenti facoltativi

La definizione di un metodo può specificare che i parametri sono obbligatori o facoltativi. I parametri sono obbligatori per impostazione predefinita. I parametri facoltativi vengono specificati includendo il valore predefinito del parametro nella definizione del metodo. Quando il metodo viene chiamato, se non sono specificati argomenti per un parametro facoltativo, viene usato il valore predefinito.

Il valore predefinito del parametro deve essere assegnato da uno dei tipi di espressioni seguenti:

  • Una costante, ad esempio una stringa letterale o un numero.

  • Espressione del form default(SomeType), dove SomeType può essere un tipo valore o un tipo riferimento. Se si tratta di un tipo riferimento, è effettivamente uguale a quello di specificare null. È possibile usare il valore letterale default, perché il compilatore può dedurre il tipo dalla dichiarazione del parametro.

  • Un'espressione del form new ValType(), dove ValType è un tipo di valore. In questo modo viene richiamato il costruttore implicito senza parametri del tipo di valore, che non è un membro effettivo del tipo.

    Nota

    In C# 10 e versioni successive, quando un'espressione del modulo new ValType() richiama il costruttore senza parametri definito in modo esplicito di un tipo valore, il compilatore genera un errore come valore del parametro predefinito deve essere una costante in fase di compilazione. Usare l'espressione default(ValType) o il valore letterale default per fornire il valore del parametro predefinito. Per ulteriori informazioni sui costruttori senza parametri, consultare la sezione Inizializzazione struct e valori predefiniti dell'articolo Tipi di struttura.

Se un metodo include parametri obbligatori e facoltativi, i parametri facoltativi sono definiti alla fine dell'elenco di parametri, dopo tutti i parametri obbligatori.

L'esempio seguente definisce un metodo, ExampleMethod, con un parametro obbligatorio e due facoltativi.

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 viene richiamato un metodo con più argomenti facoltativi usando argomenti posizionali, il chiamante deve specificare un argomento per tutti i parametri facoltativi, dal primo all'ultimo parametro per il quale è specificato un argomento. Nel caso del metodo ExampleMethod, ad esempio, se il chiamante specifica un argomento per il parametro description, deve specificarne uno anche per il parametro optionalInt. opt.ExampleMethod(2, 2, "Addition of 2 and 2"); è una chiamata a un metodo valido; opt.ExampleMethod(2, , "Addition of 2 and 0"); genera un errore di compilazione "Argomento mancante".

Se un metodo viene chiamato usando argomenti denominati o una combinazione di argomenti posizionali e denominati, il chiamante può omettere tutti gli argomenti successivi all'ultimo argomento posizionale nella chiamata al metodo.

L'esempio seguente esegue una chiamata al metodo ExampleMethod tre volte. Le prime due chiamate al metodo usano argomenti posizionali. La prima omette entrambi gli argomenti facoltativi, mentre la seconda omette l'ultimo argomento. La terza chiamata al metodo specifica un argomento posizionale per il parametro obbligatorio, ma usa un argomento denominato per specificare un valore per il parametro description omettendo l'argomento 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

L'uso di parametri facoltativi ha effetto sulla risoluzione dell'overload o sulla modalità con la quale il compilatore C# determina l'overload specifico da richiamare tramite una chiamata al metodo come descritto di seguito:

  • Un metodo, un indicizzatore o un costruttore è un candidato per l'esecuzione se ogni parametro è facoltativo o corrisponde, per nome o per posizione, a un solo argomento nell'istruzione chiamante e tale argomento può essere convertito nel tipo del parametro.
  • Se è disponibile più di un candidato, agli argomenti specificati in modo esplicito vengono applicate le regole di risoluzione dell'overload per le conversioni preferite. Gli argomenti omessi per i parametri facoltativi vengono ignorati.
  • Se due candidati sono giudicati ugualmente validi, la preferenza va a un candidato che non ha parametri facoltativi per i quali sono stati omessi gli argomenti nella chiamata. Si tratta di una conseguenza di una preferenza generale nella risoluzione dell'overload per i candidati che hanno un numero di parametri inferiore.

Valori restituiti

I metodi possono restituire un valore al chiamante. Se il tipo restituito, ovvero il tipo elencato prima del nome del metodo, non è void, il metodo può restituire il valore usando la parola chiave return. Un'istruzione con la parola chiave return seguita da una variabile, una costante o un'espressione corrispondente al tipo restituito restituirà tale valore al chiamante del metodo. Per usare la parola chiave return per restituire un valore, sono obbligatori metodi con un tipo restituito non void. La parola chiave return interrompe anche l'esecuzione del metodo.

Se il tipo restituito è void, un'istruzione return senza un valore è tuttavia utile per interrompere l'esecuzione del metodo. Senza la parola chiave return , l'esecuzione del metodo verrà interrotta quando verrà raggiunta la fine del blocco di codice.

Ad esempio, questi due metodi usano la parola chiave return per restituire numeri interi:

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

    public int SquareANumber(int number) =>
        number * number;
}

Per usare un valore restituito da un metodo, il metodo chiamante può usare la chiamata al metodo stessa ovunque è sufficiente un valore dello stesso tipo. È inoltre possibile assegnare il valore restituito a una variabile. I due esempi seguenti di codice ottengono lo stesso risultato:

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

L'uso di una variabile locale, in questo caso result, per archiviare un valore è facoltativo. Potrebbe migliorare la leggibilità del codice o potrebbe essere necessario se si desidera archiviare il valore originale dell'argomento per l'intero ambito del metodo.

In alcuni casi, si vuole che il metodo restituisca più di un singolo valore. A tale scopo, è possibile usare tipi di tupla e valori letterali di tupla. Il tipo di tupla definisce i tipi di dati degli elementi della tupla. I letterali di tupla specificano i valori effettivi della tupla restituita. Nell'esempio seguente (string, string, string, int) definisce il tipo di tupla restituito dal metodo GetPersonalInfo. L'espressione (per.FirstName, per.MiddleName, per.LastName, per.Age) è il letterale della tupla; il metodo restituisce il nome iniziale, centrale e finale e la durata di un oggetto PersonInfo.

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

Il chiamante può quindi usare la tupla restituita con codice simile al seguente:

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

È anche possibile assegnare i nomi agli elementi della tupla nella definizione del tipo della tupla. L'esempio seguente mostra una versione alternativa del metodo GetPersonalInfo che usa elementi denominati:

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

La chiamata precedente al metodo GetPersonalInfo può quindi essere modificata come segue:

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

Se a un metodo viene passata una matrice come argomento e il metodo modifica il valore dei singoli elementi, non è necessario che il metodo restituisca la matrice, anche se è possibile scegliere di effettuare questa operazione per ragioni di stile o per il flusso funzionale di valori. L'operazione non è necessaria perché C# passa tutti i tipi riferimento per valore e il valore di un riferimento a una matrice è il puntatore alla matrice. Nell'esempio seguente le modifiche al contenuto della matrice values eseguite nel metodo DoubleValues sono rilevabili da qualsiasi codice che include un riferimento alla matrice.


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

Metodi di estensione

In genere, è possibile aggiungere un metodo a un tipo esistente in due modi:

  • Modificare il codice sorgente per il tipo. Questa operazione non è possibile se non si è proprietari del codice sorgente del tipo. Questa operazione diventa una modifica importante se si aggiungono anche tutti i campi dati privati per supportare il metodo.
  • Definire il nuovo metodo in una classe derivata. Non è possibile aggiungere un metodo in questo modo usando l'ereditarietà per gli altri tipi, ad esempio strutture ed enumerazioni. Questo modo non può essere usato neanche per "aggiungere" un metodo a una classe sealed.

I metodi di estensione consentono di "aggiungere" un metodo a un tipo esistente senza modificare il tipo o implementare il nuovo metodo in un tipo ereditato. È necessario anche che il metodo di estensione non si trovi nello stesso assembly del tipo che estende. Un metodo di estensione viene chiamato come se fosse un membro definito di un tipo.

Per altre informazioni, vedere Metodi di estensione.

Metodi asincroni

Tramite la funzionalità async, è possibile richiamare i metodi asincroni senza usare callback espliciti o suddividere manualmente il codice in più metodi o espressioni lambda.

Se si contrassegna un metodo con il modificatore async , è possibile usare l'operatore await nel metodo. Quando il controllo raggiunge un'espressione await nel metodo asincrono, il controllo torna al chiamante se l'attività attesa non è stata completata e l'avanzamento nel metodo con la parola chiave await viene sospeso fino al completamento dell'attività attesa. Una volta completata l'attività, l'esecuzione del metodo può riprendere.

Nota

Un metodo async viene restituito al chiamante quando rileva il primo oggetto atteso che non è ancora completo o raggiunge la fine del metodo async, qualunque si verifichi prima.

Un metodo asincrono ha in genere un tipo restituito di Task<TResult>, Task, IAsyncEnumerable<T> o void. Il tipo restituito void viene usato principalmente per definire i gestori eventi in cui è necessario un tipo restituito void. Un metodo asincrono che restituisce void non può essere atteso e il chiamante di un metodo che restituisce void non può intercettare le eccezioni generate dal metodo. Un metodo asincrono può avere qualsiasi tipo restituito simile a un'attività.

Nell'esempio seguente DelayAsync è un metodo asincrono con un'istruzione return che restituisce un valore Integer. Poiché si tratta di un metodo asincrono, la dichiarazione del metodo deve avere un tipo restituito di Task<int>. Poiché il tipo restituito è Task<int>, la valutazione dell'espressione await in DoSomethingAsync genera un numero intero come dimostra l'istruzione int result = await delayTask seguente.

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

Un metodo asincrono non può dichiarare parametri in, ref o out, ma può chiamare metodi che hanno tali parametri.

Per ulteriori informazioni sulla funzionalità dei metodi asincroni, Consultare Programmazione asincrona con async e await e Tipi async restituiti.

Membri con corpo di espressione

È comune disporre di definizioni di metodo che semplicemente restituiscono subito il risultato di un'espressione o che includono una singola istruzione come corpo del metodo. Esiste una sintassi breve per definire tali metodi 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 il metodo restituisce void o è un metodo asincrono, il corpo del metodo deve essere un'espressione di istruzione (come per le espressioni lambda). Per le proprietà e gli indicizzatori, devono essere di sola lettura e non è necessario usare la parola chiave della funzione di accesso get.

Iteratori

Un iteratore esegue un'iterazione personalizzata su una raccolta, ad esempio un elenco o una matrice. Un iteratore usa l'istruzione yield return per restituire un elemento per volta. Quando viene raggiunta un'istruzione yield return, viene memorizzata la posizione corrente in modo che il chiamante possa richiedere l'elemento successivo della sequenza.

Il tipo restituito di un iteratore può essere IEnumerable, IEnumerable<T>, IAsyncEnumerable<T>, IEnumerator o IEnumerator<T>.

Per altre informazioni, vedere Iteratori.

Vedi anche