Bisogna partire dal presupposto che le classi sono dei Reference Type, quindi ogni volta che si parla di una variabile che è istanza di una certa classe si sta di fatto parlando di puntatori a memoria.

Le classi vanno immaginate come delle blueprint dove viene definita staticamente la struttura dei dati, struttura che poi ci servirà come “stampino” per istanziare e definire gli oggetti che a runtime ci permetteranno di leggere e scrivere gli attributi della classe.

Per dichiarare una classe usiamo la keyword class seguita da un nome, il quale per convezione è buona norma far iniziare con la lettera maiuscola, ricorrendo al camel-case nel caso in cui si tratti di un nome composto. Alcuni esempi:

class TestClass {};

class  Person {};

class  ClassB {};

All’interno di una classe troviamo i seguenti elementi:

  • Metodi: funzioni e algoritmi che svolgono delle operazioni interne alla classe;
  • Properties: costrutti interni alla classe che permettono di leggere, tramite getter, o scrivere, tramite setter, lo stato degli oggetti della classe. Di norma vanno dichiarati con la prima lettera maiuscola;
  • Fields: variabili interne di una classe, in generale sono dichiarate come private e sono accessibili solo tramite properties. Identificano tutte le info rappresentanti lo stato interno del nostro oggetto e di norma vanno dichiarate con la prima lettera minuscola;
  • Events e Delegates;
  • Nested Classes o classi annidate: classi interne definibili nel corpo della classe;

Ci sono due concetti fondamentali quando si parla di classi e OOP, ovvero:

Il concetto dell’ereditarietà ci consente di derivare, o sottoclassare, una classe mediante due elementi, ovvero: la BaseClass (superclasse o classe di base) e la DerivedClass, la quale estende la classe di base. La dichiarazione a livello di codice avviene nel seguente modo:

class DerivedClass : BaseClass {}

L’altro concetto fondamentale quando si parla di Object Oriented Programming sono le interfacce. Mentre una classe può estendere un’unica superclasse, una classe può implementare più interfacce, introducendoci così il concetto di implementazione. Chiaramente vi possono essere delle classi che estendono una superclasse e contemporaneamente implementano una o più interfacce. A livello di codice la dichiarazione avviene nel seguente modo:

class ImplClass : IFace1, IFace2 {}

class ImplDerivedClass : BaseClass, IFace1 {}

Esempio di una classe con all’interno fields e properties:

public class Person {

private DateTime birthday; // Field

public string Name { get; set; } // Property

public string Surname { get; set; } // Property

 

// Property

public DateTime Birthday {

get {

return birthday;

}

set {

if (value.Year > 1900 && value.Year <= DateTime.Today.Year) {

birthday = value;

} else {

throw new ArgumentOutOfRangeException();

 }

}

}

}

In questo esempio abbiamo due properties: Name e Surname, con i loro rispetti getter e setter, quindi quando istanziamo un oggetto di tipo Person possiamo leggere e scrivere in qualsiasi momento sia Name che Surname.

Per quanto riguarda la data di nascita, invece, il modo in cui viene gestita è un classico esempio di come sia possibile disaccoppiare field e properties. Difatti ad inizio classe viene definito un field di nome birthday, il quale è accessibile però solo tramite la property definita di seguito dal nome Birthday. In questa property sono stati definiti il getter, il quale ritorna semplicemente il valore del field birthday al chiamante, e il setter, il quale invece è un po’ più articolato e tramite un piccolo controllo valida la correttezza del valore inserito.

 

Il materiale è tratto dalle lezioni IPID svolte da Marco Pirruccio di Heartwood Labs/Operaludica.