Implementare i metodi e i parametri della classe
Un metodo è un blocco di codice che contiene una serie di istruzioni. Un programma fa sì che le istruzioni vengano eseguite chiamando il metodo e specificando gli argomenti del metodo richiesti. In C# ogni istruzione eseguita viene eseguita nel contesto di un metodo.
Firme del metodo
I metodi vengono dichiarati in una classe, un record o uno struct specificando:
- Livello di accesso facoltativo, ad esempio
publicoprivate. Il valore predefinito èprivate. - Modificatori facoltativi, ad esempio
abstractosealed. - Valore restituito o
voidse il metodo non ha alcun valore. - Nome del metodo.
- Qualsiasi parametro del metodo. I parametri del metodo sono racchiusi tra parentesi e sono separati da virgole. Le parentesi vuote indicano che il metodo non richiede parametri.
Queste parti interagiscono per formare la firma del metodo.
Importante
Un tipo restituito di un metodo non fa parte della firma del metodo ai fini dell'overload del metodo. Tuttavia, fa parte della firma del metodo quando si determina la compatibilità tra un delegato e il metodo a cui punta.
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 di overload, Drive. I due metodi Drive hanno lo stesso nome, ma tipi di parametro diversi.
Chiamata al metodo
I metodi possono essere 'istanza o statici. È necessario creare un'istanza di un oggetto per richiamare un metodo di istanza su tale istanza; un metodo di istanza opera su tale istanza e sui relativi dati. Richiamare un metodo statico facendo riferimento al nome del tipo a cui appartiene il metodo; i metodi statici non operano sui dati dell'istanza. Il tentativo di chiamare un metodo statico tramite un'istanza dell'oggetto genera un errore del compilatore.
La chiamata a un metodo è simile 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 metodo statico), 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 necessari. Quando un chiamante richiama il metodo, fornisce valori concreti, denominati 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 il metodo Square una variabile di tipo int denominata num; la seconda, una costante numerica; e la terza, 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;
}
}
Parametri di valore e riferimento
I tipi in C# sono tipi valore o tipi riferimento. Per impostazione predefinita, entrambi i tipi valore e i tipi riferimento vengono passati per valore a un metodo.
Parametri del tipo di valore
Quando un tipo valore viene passato a un metodo per valore, una copia dell'oggetto anziché l'oggetto stesso viene passata al metodo . Pertanto, le modifiche all'oggetto nel metodo chiamato non hanno alcun effetto sull'oggetto originale quando il controllo torna al chiamante.
Nell'esempio seguente viene passato un tipo 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 valore, inizializza il valore su 20 e lo passa a un metodo denominato ModifyValue che modifica il valore della variabile su 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 un oggetto di un tipo riferimento viene passato a un metodo per valore, viene passato un riferimento all'oggetto per valore. Ovvero, il metodo riceve non l'oggetto stesso, ma un argomento che indica la posizione dell'oggetto. Se si modifica un membro dell'oggetto utilizzando questo riferimento, la modifica viene riflessa nell'oggetto quando il controllo torna al metodo chiamante. Tuttavia, la sostituzione dell'oggetto passato al metodo non ha alcun effetto sull'oggetto originale quando il controllo torna al chiamante.
Nell'esempio seguente viene definita 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. Questo esempio esegue essenzialmente la stessa operazione dell'esempio precedente (passa un argomento per valore a un metodo). Tuttavia, il risultato è diverso perché viene usato un tipo riferimento anziché un tipo valore. La modifica apportata in ModifyObject al campo obj.value modifica anche il campo value dell'argomento, rt. Quando il metodo Main visualizza il valore di rt si noterà che è stato aggiornato a 33, come illustrato nell'output dell'esempio.
public class SampleRefType
{
public int value;
}
public static class ByRefTypeExample
{
public static void Main()
{
var rt = new SampleRefType { value = 44 };
Console.WriteLine("In Main, rt.value = {0}", rt.value);
ModifyObject(rt);
Console.WriteLine("Back in Main, rt.value = {0}", rt.value);
}
static void ModifyObject(SampleRefType obj)
{
obj.value = 33;
Console.WriteLine("In ModifyObject, obj.value = {0}", obj.value);
}
}
// The example displays the following output:
// In Main, rt.value = 44
// In ModifyObject, obj.value = 33
// Back in Main, rt.value = 33
Parametri del tipo di riferimento
Si passa un parametro per riferimento quando si desidera modificare il valore di un argomento in un metodo e si desidera riflettere tale modifica 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 impedire comunque 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 viene modificato nel metodo ModifyValue, la modifica del valore viene riflessa 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 ref comporta lo scambio dei valori delle variabili. Si passano due variabili a un metodo per riferimento e il metodo ne scambia il contenuto. Nell'esempio seguente vengono scambiati 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.
Raccolte di parametri
In alcuni casi, il requisito che si specifica il numero esatto di argomenti per il metodo è restrittivo. Usando la parola chiave params per indicare che un parametro è una raccolta di parametri, è possibile chiamare il metodo con un numero variabile di argomenti. Il parametro contrassegnato con la parola chiave params deve essere un tipo di raccolta e deve essere l'ultimo parametro nell'elenco dei parametri del metodo.
Un chiamante può quindi richiamare il metodo in uno dei quattro modi per il parametro params:
- Passando una raccolta del tipo appropriato che contiene il numero desiderato di elementi. Nell'esempio viene utilizzata un'espressione di raccolta in modo che il compilatore crei un tipo di raccolta appropriato.
- Passando un elenco delimitato da virgole di singoli argomenti del tipo appropriato al metodo . Il compilatore crea il tipo di raccolta appropriato.
- Passando
null. - Non specificando un argomento per la raccolta di parametri.
Nell'esempio seguente viene definito un metodo denominato GetVowels che restituisce tutte le vocali da una raccolta di parametri. Il metodo Main illustra tutti e quattro i modi per richiamare il metodo . I chiamanti non devono fornire argomenti per i parametri che includono il modificatore params. In tal caso, il parametro è una raccolta vuota.
static class ParamsExample
{
static void Main()
{
string fromArray = GetVowels(["apple", "banana", "pear"]);
Console.WriteLine($"Vowels from collection expression: '{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 IEnumerable<string>? input)
{
if (input == null || !input.Any())
{
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: ''
Nota
Prima di C# 13, il modificatore params può essere usato solo con una matrice unidimensionale.
Restituire i valori dei metodi
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 parola chiave return. Un'istruzione con la parola chiave return seguita da una variabile, una costante o un'espressione che corrisponde al tipo restituito restituisce tale valore al chiamante del metodo. I metodi con un tipo restituito non inevitabile sono necessari per utilizzare la parola chiave return per restituire un valore. La parola chiave return arresta anche l'esecuzione del metodo .
Se il tipo restituito è void, un'istruzione return senza un valore è comunque utile per arrestare l'esecuzione del metodo . Senza la parola chiave return, il metodo smette di eseguire quando raggiunge 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;
}
In questo esempio vengono utilizzati membri con corpo di espressione per assegnare il valore restituito dei metodi. Questa sintassi è una sintassi abbreviata per i metodi che usano una singola istruzione (espressione) per calcolare il valore restituito.
È anche possibile scegliere di definire i metodi con un corpo dell'istruzione e un'istruzione return:
class SimpleMathExtnsion
{
public int DivideTwoNumbers(int number1, int number2)
{
return number1 / number2;
}
}
Per utilizzare un valore restituito da un metodo, il metodo chiamante può usare la chiamata al metodo stesso in qualsiasi punto in cui un valore dello stesso tipo sia sufficiente. È anche possibile assegnare il valore restituito a una variabile. Ad esempio, i tre esempi di codice seguenti hanno lo stesso obiettivo:
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);
result = obj2.DivideTwoNumbers(6,2);
// The result is 3.
Console.WriteLine(result);
In alcuni casi, si vuole che il metodo restituisca più di un singolo valore. Per restituire più valori, usare tipi di tupla e valori letterali di tupla . Il tipo di tupla definisce i tipi di dati degli elementi della tupla. I valori letterali tupla forniscono 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 valore letterale della tupla; Il metodo restituisce il primo, il secondo e il nome della famiglia, insieme all'età, 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 utilizzare la tupla restituita usando il codice seguente:
var person = GetPersonalInfo("111111111");
Console.WriteLine($"{person.Item1} {person.Item3}: age = {person.Item4}");
I nomi possono anche essere assegnati agli elementi della tupla nella definizione del tipo di 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 nel modo seguente:
var person = GetPersonalInfo("111111111");
Console.WriteLine($"{person.FName} {person.LName}: age = {person.Age}");
Se un metodo accetta una matrice come parametro e modifica il valore di singoli elementi, non è necessario che il metodo restituisca la matrice. C# passa tutti i tipi di riferimento per valore e il valore di un riferimento a una matrice è il puntatore alla matrice.
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. Esiste un collegamento alla sintassi 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 (uguale a quella delle espressioni lambda). Per le proprietà e gli indicizzatori, devono essere di sola lettura e non si usa la parola chiave della funzione di accesso get.