Metodi (Guida per programmatori 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. In un'applicazione che usa istruzioni di primo livello, il Main metodo viene generato dal compilatore e contiene tutte le istruzioni di primo livello.

Nota

Questo articolo illustra i metodi denominati. Per informazioni sulle funzioni anonime, consultare Espressioni lambda.

Firme del metodo

I metodi vengono dichiarati in una classe, uno struct o un'interfaccia specificando il livello di accesso, public ad esempio o private, modificatori facoltativi come abstract o sealed, il valore restituito, il nome del metodo e qualsiasi parametro del metodo. 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.

I parametri del metodo vengono racchiusi tra parentesi e separati da virgole. Le parentesi vuote indicano che il metodo non richiede parametri. Questa classe contiene quattro metodi:

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

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

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

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

Accesso al metodo

Chiamare un metodo su un oggetto è come accedere a un campo. Dopo il nome dell'oggetto aggiungere un punto, il nome del metodo e le parentesi. Gli argomenti vengono elencati tra parentesi e separati da virgole. I metodi della classe Motorcycle possono quindi essere chiamati come nell'esempio seguente:

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

Parametri del metodo e argomenti

La definizione del metodo specifica i nomi e i tipi di tutti i parametri obbligatori. Quando il codice chiamante chiama il metodo, fornisce valori concreti, detti argomenti, per ogni parametro. Gli argomenti devono essere compatibili con il tipo di parametro, ma il nome dell'argomento (se presente) usato nel codice chiamante non deve essere uguale al parametro denominato definito nel metodo . Ad esempio:

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

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

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

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

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

Passaggio per riferimento e passaggio per valore

Per impostazione predefinita, quando un'istanza di un tipo valore viene passata a un metodo, la copia viene passata anziché l'istanza stessa. Di conseguenza, le modifiche apportate all'argomento non hanno alcun effetto sull'istanza originale nel metodo chiamante. Per passare un'istanza di tipo valore per riferimento, usare la ref parola chiave . Per altre informazioni, vedere Passaggio di parametri di tipi di valore.

Quando viene passato un oggetto di un tipo riferimento a un metodo, viene passato un riferimento all'oggetto, 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'argomento nel metodo chiamante, anche se si passa l'oggetto per valore.

Per creare un tipo riferimento, usare la class parola chiave , come illustrato nell'esempio seguente:

public class SampleRefType
{
    public int value;
}

Se ora si passa un oggetto basato su questo tipo a un metodo, viene passato un riferimento all'oggetto. Nell'esempio seguente viene passato un oggetto di tipo SampleRefType al metodo ModifyObject:

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

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

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 apportata in ModifyObject al campo value del parametro, obj, cambia anche il campo value dell'argomento, rt, nel metodo TestRefType . Il metodo TestRefType visualizza 33 come output.

Per altre informazioni su come passare i tipi di riferimento per riferimento e per valore, vedere Passaggio di parametri di tipi di riferimento e Tipi di riferimento.

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 l'istruzionereturn. Un'istruzione con la parola chiave return seguita da un valore corrispondente al tipo restituito restituirà tale valore al chiamante del metodo.

Il valore può essere restituito al chiamante per valore o per riferimento. I valori vengono restituiti al chiamante per riferimento se la parola chiave ref viene usata nella firma del metodo e se segue ogni parola chiave return. Ad esempio, la firma del metodo e l'istruzione return seguenti indicano che il metodo restituisce una variabile denominata estDistance per riferimento al chiamante.

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

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. Per usare la parola chiave return per restituire un valore, sono obbligatori metodi con un tipo restituito non void. 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.

Per usare un valore restituito da un metodo per riferimento, è necessario dichiarare una variabile locale ref se si vuole modificarne il valore. Se, ad esempio, il metodo Planet.GetEstimatedDistance restituisce un valore Double per riferimento, è possibile definirlo come variabile locale ref con un codice simile al seguente:

ref double distance = ref Planet.GetEstimatedDistance();

Non è necessario restituire una matrice multidimensionale da un metodo, M, che modifica il contenuto della matrice, se la funzione chiamante ha passato la matrice a M. Si può restituire la matrice risultante da M per un flusso di valori corretto o funzionale, ma non è necessario perché C# passa tutti i tipi riferimento per valore e il valore di un riferimento a una matrice è il puntatore alla matrice. Nel metodo Mtutte le modifiche apportate al contenuto della matrice sono osservabili da qualsiasi codice con un riferimento alla matrice, come illustrato nell'esempio seguente:

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

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

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 e l'avanzamento nel metodo 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à.

Nel seguente esempio, DelayAsync è un metodo asincrono con un tipo restituito Task<TResult>. DelayAsync ha un'istruzione return che restituisce un numero intero. La dichiarazione del metodo di DelayAsync deve quindi avere un tipo restituito Task<int>. Poiché il tipo restituito è Task<int>, la valutazione dell'espressione await in DoSomethingAsync genera un numero intero come illustra l'istruzione seguente: int result = await delayTask.

Il Main metodo è un esempio di metodo asincrono con un tipo restituito di Task. Passa al DoSomethingAsync metodo e, poiché è espresso con una singola riga, può omettere le async parole chiave e await . Poiché DoSomethingAsync è un metodo asincrono, l'attività per la chiamata a DoSomethingAsync deve essere attesa, come mostra l'istruzione seguente: await DoSomethingAsync();.

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

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

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

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

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

Un metodo asincrono non può dichiarare parametri 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.

Definizioni del corpo dell'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, la posizione corrente nel codice viene memorizzata. L'esecuzione viene riavviata a partire da quella posizione la volta successiva che viene chiamato l'iteratore.

Per chiamare un iteratore dal codice client, usare un'istruzione foreach .

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

Per altre informazioni, vedere Iteratori.

Specifiche del linguaggio C#

Per altre informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il riferimento ufficiale principale per la sintassi e l'uso di C#.

Vedi anche