Vai al contenuto

Funzioni e metodi

Organizzare il codice in blocchi riutilizzabili per programmi più puliti e manutenibili.

🖥️ In pratica con Windows Forms: Ripetere e riutilizzare: cicli e metodi.

Introduzione ai Metodi

I metodi (o funzioni) sono blocchi di codice che eseguono specifiche operazioni e possono essere chiamati da altre parti del programma. Sono fondamentali per applicare il principio DRY (Don't Repeat Yourself), consentendo di riutilizzare lo stesso codice più volte senza doverlo riscrivere.

Perché Usare i Metodi?

Immagina di dover calcolare l'area di un rettangolo in 10 punti diversi del tuo programma. Senza metodi, dovresti scrivere base * altezza 10 volte! Con un metodo CalcolaArea(base, altezza), lo scrivi una volta e lo richiami quando serve.

📚 Metodi vs Funzioni

In C#, poiché è un linguaggio orientato agli oggetti, si parla principalmente di "metodi", che sono funzioni che appartengono a una classe. Tuttavia, i concetti di base sono gli stessi delle funzioni in altri linguaggi come JavaScript o Python.

✨ Vantaggi dei Metodi

Riutilizzo del codice

Scrivi una volta, usa ovunque

Leggibilità

Codice organizzato e comprensibile

Manutenibilità

Modifiche in un solo punto

Testing

Facile testare singole funzionalità

Definizione di un Metodo

La sintassi base per definire un metodo in C# segue una struttura precisa con diversi componenti.

Sintassi Base Metodo

modificatoreAccesso tipoDiRitorno NomeMetodo(parametri)
{
    // corpo del metodo
    return valoreDiRitorno; // se il tipo di ritorno non è void
}

Componenti di un Metodo

Componente Descrizione Esempio
Modificatore accesso Determina la visibilità del metodo public, private
Tipo di ritorno Tipo di dato restituito (o void) int, string, void
Nome metodo Identificatore, verbo in PascalCase CalcolaSomma, GetNome
Parametri Input del metodo (opzionali) (int a, int b)
Corpo Codice eseguito alla chiamata { return a + b; }
Return Restituisce valore (se non void) return risultato;

Esempio Annotato

// ↓ Modificatore   ↓ Tipo ritorno   ↓ Nome      ↓ Parametri
public              int              CalcolaSomma(int a, int b)
{
    // ↓ Corpo del metodo
    int risultato = a + b;

    // ↓ Return (restituisce il valore)
    return risultato;
}

Tipologie di Metodi

  1. Metodo Void - Senza Parametri e Senza Ritorno

Esegue un'azione ma non restituisce nulla e non accetta input.

Metodo Void Base

public void SalutaUtente()
{
    Console.WriteLine("Benvenuto al programma!");
}

// Chiamata
SalutaUtente();  // Output: Benvenuto al programma!
  1. Metodo Void - Con Parametri ma Senza Ritorno

Accetta input ma non restituisce valori.

Metodo con Parametri

public void SalutaPersona(string nome)
{
    Console.WriteLine($"Ciao {nome}!");
}

// Chiamate
SalutaPersona("Marco");  // Output: Ciao Marco!
SalutaPersona("Anna");   // Output: Ciao Anna!
  1. Metodo con Parametri e Valore di Ritorno

Accetta input e restituisce un risultato.

Metodo con Return

public int Somma(int a, int b)
{
    return a + b;
}

// Utilizzo
int risultato = Somma(5, 3);
Console.WriteLine(risultato);  // Output: 8

// Oppure direttamente
Console.WriteLine(Somma(10, 20));  // Output: 30
  1. Metodo con Parametri Opzionali

Parametri con valori predefiniti che possono essere omessi.

Parametri Opzionali

public void ConfiguraUtente(string nome, int età = 18, bool isAdmin = false)
{
    Console.WriteLine($"Utente: {nome}, Età: {età}, Admin: {isAdmin}");
}

// Chiamate diverse - tutti validi!
ConfiguraUtente("Mario", 25, true);
// Output: Utente: Mario, Età: 25, Admin: True

ConfiguraUtente("Luigi", 30);
// Output: Utente: Luigi, Età: 30, Admin: False

ConfiguraUtente("Peach");
// Output: Utente: Peach, Età: 18, Admin: False

📌 Regole Parametri Opzionali

  • I parametri opzionali devono essere dopo quelli obbligatori
  • Il valore di default viene assegnato con =
  • Puoi omettere i parametri opzionali dalla fine
  • Esempio: Method(string nome, int età = 18) ✅
  • Errore: Method(int età = 18, string nome) ❌

Passaggio Parametri: Valore vs Riferimento

In C# esistono due modi principali per passare parametri ai metodi: per valore (default) e per riferimento.

Passaggio per Valore (Default)

Viene passata una copia del valore. Le modifiche al parametro nel metodo NON influenzano la variabile originale.

Passaggio per Valore

public void IncrementaNumero(int numero)
{
    numero += 10;  // Modifica solo la copia locale
    Console.WriteLine($"Dentro il metodo: {numero}");
}

int x = 5;
IncrementaNumero(x);
// Output dentro metodo: 15

Console.WriteLine($"Fuori dal metodo: {x}");
// Output fuori: 5 (INVARIATO!)

Passaggio per Riferimento (ref)

Viene passato un riferimento alla variabile originale. Le modifiche influenzano la variabile originale.

Passaggio per Riferimento

public void IncrementaNumero(ref int numero)
{
    numero += 10;  // Modifica la variabile originale
    Console.WriteLine($"Dentro il metodo: {numero}");
}

int x = 5;
IncrementaNumero(ref x);  // ⚠️ Serve 'ref' anche nella chiamata!
// Output dentro metodo: 15

Console.WriteLine($"Fuori dal metodo: {x}");
// Output fuori: 15 (MODIFICATO!)

⚠️ IMPORTANTE: ref richiede inizializzazione

Quando usi ref, la variabile DEVE essere inizializzata prima della chiamata:

// ❌ ERRORE
int y;  // Non inizializzata
IncrementaNumero(ref y);  // ERRORE!

// ✅ CORRETTO
int y = 0;  // Inizializzata
IncrementaNumero(ref y);  // OK

Parametri di Output (out)

Il modificatore out permette a un metodo di restituire più valori. A differenza di ref, la variabile NON deve essere inizializzata prima.

Parametri out

public bool TryDividi(int a, int b, out double risultato)
{
    if (b == 0)
    {
        risultato = 0;  // Deve essere assegnato prima del return
        return false;   // Indica fallimento
    }

    risultato = (double)a / b;
    return true;  // Indica successo
}

// Utilizzo - la variabile NON deve essere inizializzata
if (TryDividi(10, 2, out double ris))
{
    Console.WriteLine($"Risultato: {ris}");  // Output: Risultato: 5
}
else
{
    Console.WriteLine("Divisione non valida");
}

💡 Pattern Try-

Il pattern Try... con out è molto comune in C# per operazioni che possono fallire. Esempi: int.TryParse(), Dictionary.TryGetValue().

  • Restituisce bool per indicare successo/fallimento
  • Usa out per restituire il valore effettivo
  • Evita eccezioni per situazioni comuni

📊 Confronto: Valore vs ref vs out

Caratteristica Valore (default) ref out
Inizializzazione Non necessaria Obbligatoria Non necessaria
Modifica originale ❌ No ✅ Sì ✅ Sì
Assegnazione nel metodo Opzionale Opzionale Obbligatoria
Uso principale Input normale Modificare input Restituire valori extra

Overloading dei Metodi

L'overloading (sovraccarico) permette di definire più metodi con lo stesso nome ma con parametri diversi (numero o tipo).

Esempio Overloading

// Versione 1: due int
public int Somma(int a, int b)
{
    return a + b;
}

// Versione 2: due double
public double Somma(double a, double b)
{
    return a + b;
}

// Versione 3: tre int
public int Somma(int a, int b, int c)
{
    return a + b + c;
}

// Versione 4: array di int
public int Somma(params int[] numeri)
{
    int totale = 0;
    foreach (int num in numeri)
    {
        totale += num;
    }
    return totale;
}

Utilizzo Overloading

// Il compilatore sceglie automaticamente la versione corretta!
int ris1 = Somma(5, 3);              // Chiama versione 1 → 8
double ris2 = Somma(2.5, 3.5);       // Chiama versione 2 → 6.0
int ris3 = Somma(1, 2, 3);           // Chiama versione 3 → 6
int ris4 = Somma(1, 2, 3, 4, 5);     // Chiama versione 4 → 15

📌 Regole per l'Overloading

  • Il nome deve essere identico
  • I parametri devono differire per:
    • Numero di parametri
    • Tipo di parametri
    • Ordine dei parametri
  • Il tipo di ritorno da solo NON basta per l'overloading
  • Utile per creare API flessibili e intuitive

💡 params - Numero Variabile di Parametri

La keyword params permette di passare un numero variabile di argomenti dello stesso tipo. Deve essere l'ultimo parametro.

public void StampaNumeri(params int[] numeri)
{
    foreach (int num in numeri)
    {
        Console.WriteLine(num);
    }
}

// Tutte queste chiamate sono valide!
StampaNumeri(1);
StampaNumeri(1, 2, 3);
StampaNumeri(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Scope delle Variabili

Lo scope (ambito di visibilità) determina dove una variabile può essere utilizzata nel codice. Dipende da dove viene dichiarata.

  1. Variabili Locali

Dichiarate dentro un metodo, visibili solo in quel metodo.

Variabili Locali

public void Metodo1()
{
    int x = 5;  // Variabile locale
    Console.WriteLine(x);  // ✅ OK
}

public void Metodo2()
{
    Console.WriteLine(x);  // ❌ ERRORE: x non esiste qui
}
  1. Variabili di Classe (Campi)

Dichiarate a livello di classe, visibili in tutti i metodi della classe.

Campi di Classe

class Esempio
{
    // Campo di classe (field)
    private int numero = 10;

    public void Metodo1()
    {
        Console.WriteLine(numero);  // ✅ OK - campo accessibile
        numero = 20;  // ✅ Posso modificarlo
    }

    public void Metodo2()
    {
        Console.WriteLine(numero);  // ✅ OK - sempre accessibile
    }
}
  1. Variabili di Blocco

Dichiarate dentro un blocco (if, for, while), visibili solo in quel blocco.

Variabili di Blocco

public void Esempio()
{
    int x = 5;  // Variabile locale del metodo

    if (x > 0)
    {
        int y = x * 2;  // Variabile di blocco
        Console.WriteLine(y);  // ✅ OK qui
    }

    Console.WriteLine(y);  // ❌ ERRORE: y non esiste fuori dal blocco

    for (int i = 0; i < 5; i++)
    {
        int z = i * 2;  // Variabile di blocco del for
    }

    Console.WriteLine(i);  // ❌ ERRORE: i esiste solo nel for
    Console.WriteLine(z);  // ❌ ERRORE: z esiste solo nel for
}

🎯 Regole dello Scope

  • Le variabili sono visibili solo nel loro scope e in quelli interni
  • Non puoi dichiarare due variabili con lo stesso nome nello stesso scope
  • Una variabile locale può "nascondere" un campo di classe con lo stesso nome
  • Le variabili di blocco vengono distrutte quando il blocco termina

Esempio Completo Scope

class EsempioScope
{
    // ① Campo di classe - visibile ovunque nella classe
    private int campoDiClasse = 100;

    public void MetodoEsempio()
    {
        // ② Variabile locale - visibile solo in questo metodo
        int variabileLocale = 50;

        Console.WriteLine(campoDiClasse);   // ✅ OK
        Console.WriteLine(variabileLocale); // ✅ OK

        if (variabileLocale > 0)
        {
            // ③ Variabile di blocco - solo dentro l'if
            int variabileBlocco = 25;

            Console.WriteLine(campoDiClasse);     // ✅ OK
            Console.WriteLine(variabileLocale);   // ✅ OK
            Console.WriteLine(variabileBlocco);   // ✅ OK
        }

        // Console.WriteLine(variabileBlocco); // ❌ ERRORE
    }

    public void AltroMetodo()
    {
        Console.WriteLine(campoDiClasse);   // ✅ OK
        // Console.WriteLine(variabileLocale); // ❌ ERRORE
    }
}

Esempio Completo: Calcolatrice Avanzata

Vediamo un esempio completo che utilizza tutti i concetti appresi: metodi, overloading, parametri out, e organizzazione del codice.

Calcolatrice.cs - Classe Completa

using System;

class Calcolatrice
{
    // ===== OPERAZIONI BASE =====

    public int Somma(int a, int b)
    {
        return a + b;
    }

    public double Somma(double a, double b)  // Overloading
    {
        return a + b;
    }

    public int Sottrazione(int a, int b)
    {
        return a - b;
    }

    public int Moltiplicazione(int a, int b)
    {
        return a * b;
    }

    // Divisione con gestione errore
    public double Divisione(int a, int b)
    {
        if (b == 0)
        {
            throw new DivideByZeroException("Impossibile dividere per zero");
        }
        return (double)a / b;
    }

    // ===== METODI AVANZATI =====

    // Pattern Try- con out parameter
    public bool TryDivisione(int a, int b, out double risultato)
    {
        if (b == 0)
        {
            risultato = 0;
            return false;
        }

        risultato = (double)a / b;
        return true;
    }

    // Metodo con params per numero variabile di parametri
    public int SommaMultipla(params int[] numeri)
    {
        int totale = 0;
        foreach (int num in numeri)
        {
            totale += num;
        }
        return totale;
    }

    // Metodo che determina quale operazione eseguire
    public bool CalcolaRisultato(int a, int b, char operazione, out double risultato)
    {
        risultato = 0;

        switch (operazione)
        {
            case '+':
                risultato = Somma(a, b);
                return true;
            case '-':
                risultato = Sottrazione(a, b);
                return true;
            case '*':
                risultato = Moltiplicazione(a, b);
                return true;
            case '/':
                return TryDivisione(a, b, out risultato);
            default:
                return false;
        }
    }

    // Metodo helper per mostrare risultato
    private void MostraRisultato(string operazione, double risultato)
    {
        Console.WriteLine($"Risultato {operazione}: {risultato:F2}");
    }

    // Metodo pubblico che usa il metodo privato
    public void EseguiCalcolo(int a, int b, char op)
    {
        if (CalcolaRisultato(a, b, op, out double risultato))
        {
            MostraRisultato(op.ToString(), risultato);
        }
        else
        {
            Console.WriteLine("Operazione non valida!");
        }
    }
}

// ===== PROGRAMMA PRINCIPALE =====

class Program
{
    static void Main()
    {
        Calcolatrice calc = new Calcolatrice();

        Console.WriteLine("=== CALCOLATRICE C# ===\n");

        // Test operazioni base
        Console.WriteLine("--- Operazioni Base ---");
        Console.WriteLine($"5 + 3 = {calc.Somma(5, 3)}");
        Console.WriteLine($"10 - 4 = {calc.Sottrazione(10, 4)}");
        Console.WriteLine($"6 * 7 = {calc.Moltiplicazione(6, 7)}");

        // Test divisione sicura con TryDivisione
        Console.WriteLine("\n--- Divisione Sicura ---");
        if (calc.TryDivisione(10, 2, out double ris1))
        {
            Console.WriteLine($"10 / 2 = {ris1}");
        }

        if (!calc.TryDivisione(10, 0, out double ris2))
        {
            Console.WriteLine("10 / 0 = Errore (divisione per zero)");
        }

        // Test somma multipla (params)
        Console.WriteLine("\n--- Somma Multipla ---");
        Console.WriteLine($"Somma(1,2,3,4,5) = {calc.SommaMultipla(1, 2, 3, 4, 5)}");

        // Test calcolatrice interattiva
        Console.WriteLine("\n--- Calcolatrice Interattiva ---");
        Console.Write("Primo numero: ");
        int num1 = int.Parse(Console.ReadLine());

        Console.Write("Secondo numero: ");
        int num2 = int.Parse(Console.ReadLine());

        Console.Write("Operazione (+, -, *, /): ");
        char op = Console.ReadLine()[0];

        calc.EseguiCalcolo(num1, num2, op);
    }
}

💡 Analisi dell'Architettura

  • Separazione responsabilità - Classe Calcolatrice vs Program
  • Overloading - Somma(int, int) e Somma(double, double)
  • Pattern Try- - TryDivisione per evitare eccezioni
  • params - SommaMultipla accetta numero variabile parametri
  • out parameter - CalcolaRisultato restituisce bool + risultato
  • Metodo privato - MostraRisultato (helper interno)
  • Riutilizzo - EseguiCalcolo usa altri metodi della classe

Esercizi Pratici

Esercizio 1: Libreria Geometria

Crea una classe Geometria con i seguenti metodi:

  • CalcolaAreaRettangolo(base, altezza) → double
  • CalcolaAreaCerchio(raggio) → double (usa π = 3.14)
  • CalcolaPerimetroRettangolo(base, altezza) → double
  • Overload di CalcolaArea che accetti diversi parametri
  • Metodo TryCalcolaArea con validazione parametri (out)
Mostra la soluzione

Soluzione Esercizio 1

using System;

class Geometria
{
    private const double PI = 3.14159;

    // Area rettangolo
    public double CalcolaAreaRettangolo(double baseRett, double altezza)
    {
        return baseRett * altezza;
    }

    // Area cerchio
    public double CalcolaAreaCerchio(double raggio)
    {
        return PI * raggio * raggio;
    }

    // Perimetro rettangolo
    public double CalcolaPerimetroRettangolo(double baseRett, double altezza)
    {
        return 2 * (baseRett + altezza);
    }

    // Overload - Area quadrato (un solo parametro)
    public double CalcolaArea(double lato)
    {
        return lato * lato;
    }

    // Overload - Area rettangolo
    public double CalcolaArea(double baseRett, double altezza)
    {
        return baseRett * altezza;
    }

    // Overload - Area triangolo (3 parametri)
    public double CalcolaArea(double a, double b, double c)
    {
        // Formula di Erone
        double s = (a + b + c) / 2;
        return Math.Sqrt(s * (s - a) * (s - b) * (s - c));
    }

    // Metodo con validazione
    public bool TryCalcolaAreaRettangolo(double baseRett, double altezza, out double area)
    {
        area = 0;

        if (baseRett <= 0 || altezza <= 0)
        {
            return false;
        }

        area = baseRett * altezza;
        return true;
    }
}

class Program
{
    static void Main()
    {
        Geometria geo = new Geometria();

        // Test calcoli
        Console.WriteLine($"Area rettangolo 5x3: {geo.CalcolaAreaRettangolo(5, 3)}");
        Console.WriteLine($"Area cerchio r=4: {geo.CalcolaAreaCerchio(4):F2}");
        Console.WriteLine($"Perimetro rett 5x3: {geo.CalcolaPerimetroRettangolo(5, 3)}");

        // Test overload
        Console.WriteLine($"\nArea quadrato lato 5: {geo.CalcolaArea(5)}");
        Console.WriteLine($"Area rettangolo 4x6: {geo.CalcolaArea(4, 6)}");
        Console.WriteLine($"Area triangolo 3,4,5: {geo.CalcolaArea(3, 4, 5):F2}");

        // Test validazione
        if (geo.TryCalcolaAreaRettangolo(5, 3, out double area))
        {
            Console.WriteLine($"\nArea valida: {area}");
        }

        if (!geo.TryCalcolaAreaRettangolo(-5, 3, out area))
        {
            Console.WriteLine("Parametri non validi!");
        }
    }
}

Esercizio 2: Validatore Password

Crea una classe PasswordValidator con metodi per validare password:

  • ValidaLunghezza(password, minLength) → bool
  • ContieneMaiuscola(password) → bool
  • ContieneNumero(password) → bool
  • ValidaPassword(password, out string messaggio) → bool (verifica tutte le regole)
  • Overload di ValidaPassword con requisiti personalizzabili
Mostra la soluzione

Soluzione Esercizio 2

using System;

class PasswordValidator
{
    // Valida lunghezza minima
    public bool ValidaLunghezza(string password, int minLength)
    {
        return password.Length >= minLength;
    }

    // Verifica presenza maiuscola
    public bool ContieneMaiuscola(string password)
    {
        foreach (char c in password)
        {
            if (char.IsUpper(c))
            {
                return true;
            }
        }
        return false;
    }

    // Verifica presenza numero
    public bool ContieneNumero(string password)
    {
        foreach (char c in password)
        {
            if (char.IsDigit(c))
            {
                return true;
            }
        }
        return false;
    }

    // Validazione completa con messaggio
    public bool ValidaPassword(string password, out string messaggio)
    {
        messaggio = "";

        if (!ValidaLunghezza(password, 8))
        {
            messaggio = "Password troppo corta (min 8 caratteri)";
            return false;
        }

        if (!ContieneMaiuscola(password))
        {
            messaggio = "Password deve contenere almeno una maiuscola";
            return false;
        }

        if (!ContieneNumero(password))
        {
            messaggio = "Password deve contenere almeno un numero";
            return false;
        }

        messaggio = "Password valida!";
        return true;
    }

    // Overload con requisiti personalizzati
    public bool ValidaPassword(string password, int minLength,
                               bool richiedeMaiuscola, bool richiede Numero,
                               out string messaggio)
    {
        messaggio = "";

        if (!ValidaLunghezza(password, minLength))
        {
            messaggio = $"Password troppo corta (min {minLength} caratteri)";
            return false;
        }

        if (richiedeMaiuscola && !ContieneMaiuscola(password))
        {
            messaggio = "Password deve contenere maiuscola";
            return false;
        }

        if (richiede Numero && !ContieneNumero(password))
        {
            messaggio = "Password deve contenere numero";
            return false;
        }

        messaggio = "Password valida!";
        return true;
    }
}

class Program
{
    static void Main()
    {
        PasswordValidator validator = new PasswordValidator();

        string[] passwords = { "ciao", "Ciao1234", "password", "MyPass99" };

        Console.WriteLine("=== TEST VALIDAZIONE PASSWORD ===\n");

        foreach (string pwd in passwords)
        {
            if (validator.ValidaPassword(pwd, out string msg))
            {
                Console.WriteLine($"✅ '{pwd}': {msg}");
            }
            else
            {
                Console.WriteLine($"❌ '{pwd}': {msg}");
            }
        }

        // Test overload con requisiti custom
        Console.WriteLine("\n=== VALIDAZIONE CUSTOM (min 6, no maiuscola) ===");
        if (validator.ValidaPassword("pass123", 6, false, true, out string msgCustom))
        {
            Console.WriteLine($"✅ {msgCustom}");
        }
    }
}

🎯 Riepilogo Lezione

Concetti Fondamentali:

  • Definizione metodi
  • Parametri e return
  • void vs tipo ritorno
  • Parametri opzionali
  • Chiamata metodi

Concetti Avanzati:

  • Passaggio per valore/riferimento
  • ref e out parameters
  • Overloading metodi
  • params (varargs)
  • Scope variabili