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 Main metodo è il punto di ingresso per ogni applicazione C# ed è 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, vedere Espressioni Lambda.

Firme del metodo

I metodi vengono dichiarati in un classoggetto , recordo 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:

using System;

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 Motorcycle classe include un metodo di overload, Drive. Due metodi hanno lo stesso nome, ma devono essere differenziati in base ai relativi tipi di parametri.

Chiamata al 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. Si richiama un metodo statico facendo riferimento al nome del tipo a cui appartiene il metodo; i metodi statici non operano 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 (se si chiama un metodo di istanza) o il nome del tipo (se si chiama un static metodo), aggiungere un punto, il nome del metodo e le parentesi. Gli argomenti sono elencati tra parentesi e sono 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 uguale al parametro denominato 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 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()
    {
        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);
    }
}

È anche possibile usare argomenti denominati anziché argomenti posizionali quando si richiama un metodo. 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.

using System;

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

    public override double GetTopSpeed()
    {
        return 108.4;
    }

    static void Main()
    {

        TestMotorcycle moto = new TestMotorcycle();
        moto.StartEngine();
        moto.AddGas(15);
        var travelTime = moto.Drive(speed: 60, miles: 170);
        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.

var 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, tuttavia, non è definito nella Person classe. Viene Equals ereditato da Object.

using System;

public class Person
{
   public String FirstName;
}

public class ClassTypeExample
{
   public static void Main()
   {
      var p1 = new Person();
      p1.FirstName = "John";
      var p2 = new Person();
      p2.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.

using System;

public class Person
{
    public String FirstName;

    public override bool Equals(object obj)
    {
        var p2 = obj as Person;
        if (p2 == null)
            return false;
        else
            return FirstName.Equals(p2.FirstName);
    }

    public override int GetHashCode()
    {
        return FirstName.GetHashCode();
    }
}

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

Passaggio di parametri

I tipi in C# sono tipi di valore o tipi di riferimento. Per un elenco di tipi di valore predefiniti, vedere 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.

using System;

public class ByValueExample
{
   public static void Main()
   {
      int 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. In questo esempio viene eseguita essenzialmente la stessa cosa dell'esempio precedente, che 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.

using System;

public class SampleRefType
{
    public int value;
}

public class ByRefTypeExample
{
    public static void Main()
    {
        var rt = new SampleRefType();
        rt.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.

using System;

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

   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.

using System;

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

      Swap(ref i, ref j);

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

   static void Swap(ref int x, ref int y)
   {
      int temp = x;
      x = y;
      y = temp;
   }
}
// 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 può 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.
  • nullPassando .
  • 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 Main metodo illustra tutti e quattro i modi per richiamare il metodo . I chiamanti non devono fornire argomenti per i parametri che includono il params modificatore. In tal caso, il parametro è una matrice vuota.

using System;
using System.Linq;

class ParamsExample
{
    static void Main()
    {
        string fromArray = GetVowels(new[] { "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;
        }

        var vowels = new char[] { '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

Una definizione di metodo può specificare che i relativi parametri sono obbligatori o che sono 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 formato 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 default valore letterale, 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 perché il valore del parametro predefinito deve essere una costante in fase di compilazione. Usare l'espressione default(ValType) o il default valore letterale per specificare il valore del parametro predefinito. Per altre informazioni sui costruttori senza parametri, vedere la sezione Inizializzazione dello 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.

using System;

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 ExampleMethod metodo , ad esempio, se il chiamante fornisce un argomento per il description parametro , deve specificarne uno anche per il optionalInt parametro . 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 fornisce un argomento posizionale per il parametro obbligatorio, ma usa un argomento denominato per fornire un valore al description parametro durante l'omissione dell'argomento optionalInt .

public 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 vengono giudicati ugualmente validi, la preferenza va a un candidato che non ha parametri facoltativi per i quali gli argomenti sono stati omessi 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 (il tipo elencato prima del nome del metodo) non voidè , il metodo può restituire il valore usando la return parola chiave . 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)
    {
        return number1 + number2;
    }

    public int SquareANumber(int number)
    {
        return 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 facilmente i tipi di tupla e i 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 un metodo viene passato una matrice come argomento e modifica il valore di singoli elementi, non è necessario che il metodo restituisca la matrice, anche se è possibile scegliere di farlo per uno stile corretto o un 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.


using System;

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

   public static void DoubleValues(int[] arr)
   {
      for (int ctr = 0; ctr <= arr.GetUpperBound(0); ctr++)
         arr[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. Non è possibile eseguire questa operazione, naturalmente, 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 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. Anche il metodo di estensione non deve risiedere nello stesso assembly del tipo esteso. 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 viene completata e l'avanzamento nel metodo con la await parola chiave viene sospeso fino al completamento dell'attività attesa. Una volta completata l'attività, l'esecuzione del metodo può riprendere.

Nota

Un metodo asincrono torna al chiamante quando rileva il primo oggetto atteso che non è ancora completo o arriva alla fine del metodo asincrono, a qualsiasi condizione che si verifica per prima.

Un metodo asincrono ha in genere un tipo restituito di Task<TResult>, TaskIAsyncEnumerable<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 altre informazioni sui metodi asincroni, vedere Programmazione asincrona con tipi restituiti async e await e Async.

Membri con corpo di espressione

È comune avere definizioni di metodo che restituiscono immediatamente con il risultato di un'espressione o che hanno una singola istruzione come corpo del metodo. È disponibile un collegamento alla sintassi per la definizione di 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 si usa la parola chiave della get funzione di accesso.

Iterators

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 alla 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>, IEnumeratoro IEnumerator<T>.

Per altre informazioni, vedere Iteratori.

Vedi anche