LE CLASSI INTRODUZIONE

Con le classi in C# C sharp ci avviciniamo al cuore della programmazione Object Oriented detta anche OOP. In ogni linguaggio di programmazione orientato agli oggetti le classi sono delle entità fondamentali. Per avvicinarci a questo tipo di programmazione simuleremo la gestione di un conto corrente. Ovviamente il codice reale sarebbe ben diverso, ma per introdurre la OOP ci concentreremo su questo tipo di esempio. Adesso vediamo come si dichiara una classe in C#.

using System;
namespace DefinizioneClasse
{
    class MyClass //DEFINIZIONE DI UNA CLASSE
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            
        }
    }
}

DEFINIZIONE DI UNA CLASSE

Come vedi si usa la keyword class seguita dal nome della classe in notazione pascal case prima lettera maiuscola e l’iniziale di ogni parola sempre in maiuscolo. Non è obbligatorio seguire questa convenzione, tuttavia è la convenzione standard seguita per i nomi delle classi in C#. Come vedremo le classi possono contenere attributi che non sono altro che variabili di istanza della classe o variabili di classe se abbiamo usato la parola chiave static, dei metodi che sono le azioni compiute sugli attributi, nell’esempio di gestione di un conto corrente bancario ci saranno sicuramente due metodi Prelievo e Accredito cha vanno a modificare l’attributo saldo.

VARIABILI DI ISTANZA

Finora abbiamo visto la definizione e la dichiarazione di variabili locali, cioè variabili definite come parametri di un metodo o definite internamente al metodo. Queste variabili esistono finché è in esecuzione il metodo dove sono state dichiarate poi vengono eliminate dalla memoria come abbiamo visto. Le variabili di istanza sono variabili definite a livello di classe e rappresentano gli attributi di quella classe. Supponiamo di definire tre variabili a livello di classe myInt e myString e myBoolean. Il modificatore di accesso di default sarà private, cioè queste variabili sono visibili solo all’interno della classe in cui sono dichiarate e inaccessibili dall’esterno.

using System;
namespace DefinizioneClasse
{
    class MyClass //DEFINIZIONE DI UNA CLASSE
    {
        //DEFINIAMO TRE ATTRIBUTI DELLA CLASSE
        int myInt;
        string myString;
        bool myBoolean;
    }

    class Program
    {
        static void Main(string[] args)
        {
            
        }
    }
}

CREAZIONE DI UNA ISTANZA

Creiamo una istanza di MyClass, puoi considerare una classe come un template da cui poi verranno creati gli oggetti di quella classe detti anche istanze della classe. 

MyClass mc = new MyClass();

Ciò avviene utilizzando la keyword new, la variabile mc è detta variabile di istanza e tale variabile oggetto avrà una propria copia dell’intero myInt, della stringa myString e di myBoolean. Tali variabili che ritroviamo nell’istanza mc sono chiamate variabili di istanza e tale oggetto potrà gestirle come vuole. A differenza delle variabili locali le variabili di istanza vengono inizializzate a 0 per i tipi numerici, false per i tipi booleani e null per altri oggetti eventualmente contenuti nella classe.

 

MODIFICATORI DI ACCESSO

I modificatori di accesso regolano la visibilità a livello di classe, metodo, attributo e property. Di default gli attributi e i metodi di una classe hanno un modificatore di accesso private quindi non sono visibili all’esterno della classe e accessibili da altre classi. Quindi scrivere string myString; oppure private string myString; assume lo stesso significato. Viceversa il modificatore public rende accessibile la variabile dall’esterno quindi altri oggetti potrebbero cambiare il valore dell’attributo. Questa è una procedura sconsigliata perché va a violare l’information hiding. Per gestire correttamente l’attributo lo si rende privato e si scrivono due metodi pubblici che consentono di visualizzarlo e scriverlo, ovviamente dopo aver fatto tutti i controlli al valore che viene passato dall’esterno.

using System;
namespace DefinizioneClasse
{
    class MyClass
    {
        private string myString;
        public string GetValue()
        {
            return myString;
        }
        public void SetValue(string s)
        {
                 //Si fanno i controlli del caso, si manipola se necessario
                 //la stringa passata e come ultima operazione la si assegna
                 //a myString. 
                myString = s;
        }   

    }

    class Program
    {
        static void Main(string[] args)
        {
            
        }
    }
}

Il terzo modificatore che prendiamo in considerazione, dopo public e private è internal. Questo modificatore di accesso è il default per le classi quindi scrivere class MyClass() o internal class MyClass() è la stessa cosa. Il modificatore internal si può usare sugli attributi, metodi e classi con questa keyword di fatto rendiamo visibili metodi classi e attributi solo al proprio assembly di appartenenza, quindi a tutti gli oggetti definiti nell’assembly ma non al di fuori di esso.

LA PAROLA CHIAVE STATIC

Le variabili di una classe si suddividono in due grosse categorie.

  • Variabili di istanza.
  • Variabili di classe.

Se definiamo un oggetto partendo dalla definizione di MyClass con la keyword new;

MyClass mc = new MyClass();

L’oggetto mc creato partendo dalla definizione della classe ha tre variabili di istanza myInt, myString e myBoolean (vedere codice sopra riportato) che vanno a formare lo stato dell’oggetto in un certo momento. Se dichiariamo dieci oggetti della classe MyClass ognuno avrà la propria copia delle variabili di istanza e ognuno avrà il proprio stato interno. Viceversa usando la keyword static la variabile sarà definita una sola volta nella classe MyClass e tutte le istanze di MyClass condivideranno la stessa variabile. Questo tipo di variabili sono definite variabili di classe e si accede ad esse con la dot notations (Notazione punto).

using System;
namespace DefinizioneClasse
{
    class MyClass
    {
        
        static int myInt;
        static void MyMethod()
        {
            myInt=90;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            
        }
    }
}

Per accedere alla variabile di classe myInt occorre un metodo statico ossia un metodo anch’esso di classe quindi definito con la parola chiave static. Per accedere a myInt si usa la Dot Notations cioè: MyClass.myInt = 90; Viceversa una variabile di istanza non deve avere la keyword static, sotto viene definita una variabile e un metodo di istanza.

using System;
namespace DefinizioneClasse
{
    class MyClass
    {
        
        int myInt;
        void MyMethod()
        {
           //IMPOSTA LA VARIABILE DI ISTANZA
            myInt=90;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            
        }
    }
}

CLASSI E COSTRUTTORI STATICI

Anche una classe può essere dichiarata come static, in questo caso si sta esplicitamente indicando al compilatore che questa classe conterrà solo membri di classe, variabili e metodi e non si potranno costruire oggetti (istanze) a partire da questa classe. Questo tipo di classi vengono utilizzate quando si ha bisogno di una serie di metodi di utilità generale che verranno richiamati direttamente sulla classe, non avrebbe senso creare oggetti per questo tipo di classi. Ti anticipo il prossimo argomento, noi non abbiamo ancora parlato di costruttori, un costruttore è un metodo che prende lo stesso nome della classe e che serve a fabbricare oggetti. Quando si ha a che fare con un costruttore statico il compilatore lo chiama una volta sola quando viene invocata la classe a runtime. Il suo compito è quello di inizializzare le variabili statiche.

using System;
namespace DefinizioneClasse
{
    static class MyClass 
    {
        static int myInt;
        static MyClass()
        {
            myInt = 150;
        } 
    }
    class Program
    {
         static void Main(string[] args)
        {
            
        }
    }
}

I COSTRUTTORI

I costruttori sono metodi che obbligatoriamente hanno lo stesso nome della classe, non restituiscono valori ma servono ad inizializzare correttamente gli attributi definiti all’interno della classe. Facciamo un esempio:

using System;
namespace DefinizioneClasse
{
    class Persona
    {
        private string nome;
        private string cognome;
        private int eta;
        public Persona(string p_nome, string p_cognome, int p_eta)
        {
            nome = p_nome;
            cognome = p_cognome;
            eta = p_eta;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            
        }
    }
}

Come vedi ho definito una classe Persona con tre attributi di istanza con modificatore private e il costruttore con modificatore di accesso public in modo che possiamo creare istanze della classe da altre parti del codice. Ma vediamo come si creano le istanze della classe Persona, argomento già anticipato precedentemente.

using System;
namespace DefinizioneClasse
{
    class Persona
    {
        private string nome;
        private string cognome;
        private int eta;
        public Persona(string p_nome, string p_cognome, int p_eta)
        {
            nome = p_nome;
            cognome = p_cognome;
            eta = p_eta;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Persona p = new Persona("Mario","Rossi",54);//CREAZIONE ISTANZA DELLA CLASSE PERSONA 
        }
    }
}

Come vedi per creare oggetti della classe persona prima si dichiara il tipo, poi la variabile reference p la keyword new e il costruttore con i parametri che inizializzano le variabili di istanza della classe Persona. Con questo oggetto non possiamo fare molto visto che contiene solamente tre variabili di istanza, ma è importante capire il ruolo del costruttore che come ti ripeto serve a dare uno stato iniziale alle variabili di istanza e come debba essere invocato per costruire istanze della classe persona.

LO SCOPE O AMBITO DI VISIBILITA’ DELLE VARIABILI

Esistono tre scope o ambito di visibilità delle delle variabili che sono i seguenti:

  • Class scope
  • Method scope
  • Block scope
using System;
namespace DefinizioneClasse
{
    class Persona
   {
        //LE VARIABILI DICHIARATE A LIVELLO DI CLASSE HANNO UN CLASS SCOPE
        private string nome;
        private string cognome;
        private int eta;
        public Persona(string p_nome, string p_cognome, int p_eta)
        {
            //METHOD SCOPE
            nome = p_nome;
            cognome = p_cognome;
            eta = p_eta;
            if (p_nome == "Mario")
            {
                //LOCAL SCOPE
                string s = "Ciao Mario!";
                Console.WriteLine(s);
            }
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            
        }
    }
}

Le variabili di istanza hanno un ambito di visibilità in tutta la classe in cui sono definite, nome, cognome, eta sono accessibili all’interno del metodo costruttore. Le variabili p_nome, p_cognome, p_eta hanno uno scope a livello di costruttore, sono varIabili locali al metodo quindi utilizzabili solo all’interno del metodo costruttore. La variabile s è definita dentro un blocco if il suo ambito di visibilità è a livello di blocco if, usciti da tale blocco la variabile s non sarà più accessibile.

IL NAME HIDING

  • PRIMA REGOLA
using System;
namespace DefinizioneClasse
{
    class Program
    {
        public static void Main(string[] args)
        {
            int x = 20;
            while(true)
            {
                    int x = 10; //ERRORE
                    Console.WriteLine(x);
                    break; 
            } 
            Console.WriteLine(x);
        }
    }
}

Una variabile x definita all’interno di un metodo ha lo scope in tutto il metodo in cui è stata definita, se proviamo a dichiarare una nuova variabile x a livello di blocco while otteniamo un errore di compilazione.

  • SECONDA REGOLA
using System;
namespace DefinizioneClasse
{
    class MyClass {
        int x = 10;
        public void MyMethod()
        {
               int x = 20;
               Console.WriteLine(x); //20
        } 
    }

    class Program
    {
        public static void Main(string[] args)
        {
            
        }
    }
}

La variabile interna al metodo nasconde temporaneamente quella di istanza. È questo quello che si intende per name hiding (nascondere il nome). In altri metodi della classe dove la variabile x non viene ridefinita continua a vedersi la variabile di istanza impostata a 10.

 

  • TERZA REGOLA
using System;
namespace DefinizioneClasse
{
    class Program
    {
        public static void Main(string[] args)
        {
            for(int x = 0; x<;10; x++) 
            {
                Console.WriteLine(x);
            }
        }
    }
}

La variabile x è visibile solo all’interno del blocco di codice del ciclo for e eventualmente altri blocchi nidificati.

LA KEYWORD this

this rappresenta l’istanza corrente.

using System;
namespace DefinizioneClasse
{
    class Persona
    {
        private string nome;
        private string cognome;
        private int eta;
        public Persona(string nome, string cognome, int eta)
        {
            this.nome = nome;
            this.cognome = cognome;
            this.eta = eta;
        }
    }

    class Program
    {
        public static void Main(string[] args)
        {
            
        }
    }
}

Anteponendo la keyword this non c’è più ambiguità tra i nomi delle variabili locali al costruttore e le variabili di istanza this.nome, this.cognome e this.eta. In una classe possiamo definire più di un costruttore a patto che abbia una signature diversa, ossia un diverso numero di parametri. In questo caso si parla di overload del costruttore.

using System;
namespace DefinizioneClasse
{
    class Persona
    {
            private string nome;
            private string cognome;
            private int eta;
            public Persona(string nome, string cognome, int eta)
            {
                this.nome = nome;
                this.cognome = cognome;
                this.eta = eta;
            }
            //RICHIAMA IL COSTRUTTORE A TRE PARAMETRI
            public Persona(string cognome, int eta):this("Mario", cognome, eta)
            {
                this.cognome = cognome;
                this.eta = eta;
            }
            //CHIAMA IL COSTRUTTORE A TRE PARAMETRI
            public Persona() :this("Mario", "Rossi", 41){}
    }


    class Program
    {
        public static void Main(string[] args)
        {
            
        }
    }
}

Abbiamo creato un nuovo costruttore con due parametri con il quale intendiamo creare oggetti dalla classe Persona. Per fare ciò impostiamo il nome con un valore di default “Mario” e richiamiamo il costruttore a tre parametri con la seguente sintassi:

this(“Mario”, cognome, eta)

dove this rappresenta l’istanza corrente. È possibile creare anche un costruttore che non accetta alcun parametro, in questo caso dobbiamo richiamare il costruttore a tre parametri usando la sintassi appena vista e fornendo valori di default affinché l’oggetto sia inizializzato correttamente. Possiamo anche non definire alcun costruttore nella classe, né con parametri, né senza parametri in tal caso sarà il compilatore dietro le quinte a generarne uno senza parametri. Questo viene chiamato costruttore di default e ci permette di creare istanze da una classe pur non avendo definito un costruttore. Tutto ciò in alcune situazioni si rivela particolarmente utile.

using System;

namespace EsercitazioneClassi
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var utente1 = new Utente("1234560", "Mario", "Rossi", "4356789");
            utente1.SetSaldo(13000m);
            var utente2 = new Utente("12345678", "Mario", "Verdi", "435679879");
            utente2.SetSaldo(30000m);
            utente1.SaldoCorrente();
            utente2.SaldoCorrente();
            var b1 = new Banca("1234567890", "UBI Banca");
            var b2 = new Banca("1234576543", "UBI Banca");
            b1.Preleva(5000m,utente1);
            b2.Preleva(10000m,utente2);
            utente1.SaldoCorrente();
            utente2.SaldoCorrente();
            b1.Accredito(5000m,utente1);
            utente1.SaldoCorrente();
        }
    }

    internal class Utente
    {
        private string cognome;
        private string id;
        private string nome;
        private string numeroConto;
        private decimal saldo;

        public Utente(string id, string nome, string cognome, string numeroConto)
        {
            this.id = id;
            this.nome = nome;
            this.cognome = cognome;
            this.numeroConto = numeroConto;
        }

        internal void Denominazione()
        {
            Console.WriteLine($"Utente {this.nome + " " + this.cognome}");
        }

        internal void SaldoCorrente()
        {
            Console.WriteLine($"Utente {this.nome + " " + this.cognome} saldo: {GetSaldo()}");
        }

        internal decimal GetSaldo()
        {
            return saldo;
        }

        internal void SetSaldo(decimal importo)
        {
            if (importo < 0)
            {
                Console.WriteLine($"Saldo negativo. Utente:{this.nome + " " + this.cognome}");
                return;
            }

            saldo = importo;
        }
    }

    internal class Banca
    {
        private string abi;
        private string cab;
        private string denominazione;
        private string id;
        private Utente utente;

        public Banca(string id, string denominazione, string abi, string cab)
        {
            this.id = id;
            this.denominazione = denominazione;
            this.abi = abi;
            this.cab = cab;
        }

        public Banca(string id, string denominazione) :
            this(id, denominazione, "06098", "14400")
        {
        }

        internal void Denominazione()
        {
            Console.WriteLine($"Id Banca {this.id} ,denominazione: {this.denominazione}");
        }

        internal void Preleva(decimal importo,Utente utente)
        {
            this.utente = utente;
            var saldo = this.utente.GetSaldo();
            if (importo > saldo)
            {
                Console.WriteLine($"Operazione di prelievo non ammessa, importo richiesto {importo}");
                this.utente.Denominazione();
                return;
            }

            if (importo < 0)
            {
                Console.WriteLine($"Importo prelevato non valido: {importo}");
                return;
            }

            this.utente.SetSaldo(this.utente.GetSaldo() - importo);
            //IMPOSTO A NULL L'UTENTE
            this.utente = null;
        }

        internal void Accredito(decimal importo, Utente utente)
        {
            this.utente = utente;
            if (importo < 0)
            {
                Console.WriteLine($"Importo accreditato non valido: {importo}");
                this.utente.Denominazione();
                return;
            }

            this.utente.SetSaldo(this.utente.GetSaldo() + importo);
            //IMPOSTO A NULL L'UTENTE
            this.utente = null;
        }
    }
}

APPROFONDIMENTO SULLE CLASSI

In C#, le classi sono una delle fondamentali unità di programmazione orientata agli oggetti. Le classi servono come modelli per creare oggetti (istanze di classi) e possono contenere campi, proprietà, metodi, eventi e costruttori. Ecco una panoramica delle classi in C# con esempi per ciascuno degli elementi fondamentali:

Definizione di una Classe

Per definire una classe in C#, si utilizza la parola chiave class seguita dal nome della classe. Ecco un esempio di una classe semplice:

public class Persona
{
              // Campi
              private string nome;
              private int eta;

              // Proprietà
              public string Nome
              {
                    get { return nome; }
                    set { nome = value; }
              }

              public int Eta
              {
                    get { return eta; }
                    set { eta = value; }
              }

              // Costruttore
              public Persona(string nome, int eta)
              {
                    this.nome = nome;
                    this.eta = eta;
              }

              // Metodi
              public void Saluta()
              {
                       Console.WriteLine($”Ciao, mi chiamo {nome} e ho {eta} anni.“);
              }
}

Creazione di un’Istanza

Per creare un’istanza di una classe, si utilizza la parola chiave new seguita dal costruttore della classe:

Persona persona = new Persona(“Mario Rossi“, 30);
persona.Saluta(); // Output: Ciao, mi chiamo Mario Rossi e ho 30 anni.

Campi

I campi sono variabili dichiarate direttamente all’interno di una classe. Possono essere private, protected o public. Nell’esempio sopra, nome e eta sono campi.

Proprietà

Le proprietà permettono di controllare l’accesso ai campi. Sono simili a variabili ma includono dei metodi accessori (get) e mutatori (set). Nell’esempio sopra, Nome ed Eta sono proprietà.

Metodi

I metodi sono funzioni definite all’interno di una classe. Possono manipolare i campi della classe e possono essere invocati sulle istanze della classe. Il metodo Saluta nell’esempio sopra è un esempio di metodo.

Costruttori

I costruttori sono metodi speciali utilizzati per inizializzare le nuove istanze di una classe. Hanno lo stesso nome della classe e non restituiscono alcun valore. Il costruttore della classe Persona nell’esempio sopra è un esempio di costruttore.

Ereditarietà

C# supporta l’ereditarietà, che permette di creare una nuova classe basata su una classe esistente. La nuova classe eredita i membri della classe base ma può aggiungere nuovi membri o sovrascrivere quelli esistenti. Vedremo Property, Ereditarietà e Polimorfismo dettagliatamente in post successivi.

public class Studente : Persona
{

         public string Scuola { get; set; }

         public Studente(string nome, int eta, string scuola)
         : base(nome, eta)
         {
            Scuola = scuola;
         }

         public void Studia()
         {
            Console.WriteLine($”{Nome} studia a {Scuola}.”);
         }
}

Studente studente = new Studente(“Anna Bianchi“, 20, “Università di Roma“);
studente.Saluta(); // Output: Ciao, mi chiamo Anna Bianchi e ho 20 anni.
studente.Studia(); // Output: Anna Bianchi studia a Università di Roma.

Polimorfismo

Il polimorfismo permette di trattare gli oggetti di una classe derivata come oggetti della loro classe base. Si utilizza frequentemente con i metodi virtuali e l’override.

public class Insegnante : Persona
{
       public string Materia { get; set; }

       public Insegnante(string nome, int eta, string materia)
      : base(nome, eta)
      {
          Materia = materia;
      }

      public override void Saluta()
      {
         Console.WriteLine($”Buongiorno, sono il professor {Nome} e insegno {Materia}.”);
      }
}

Persona persona1 = new Studente(“Giorgio Verdi“, 21, “Politecnico di Milano“);
Persona persona2 = new Insegnante(“Maria Rossi“, 40, “Matematica“);

persona1.Saluta(); // Output: Ciao, mi chiamo Giorgio Verdi e ho 21 anni.
persona2.Saluta(); // Output: Buongiorno, sono il professor Maria Rossi e insegno Matematica.

Riassunto

Le classi in C# sono potenti strumenti per modellare entità del mondo reale e organizzare il codice. Con campi, proprietà, metodi, costruttori, ereditarietà e polimorfismo, le classi consentono di creare programmi flessibili e manutenibili.

LINK AI PRECEDENTI POST

LINK AL CODICE SU GITHUB