A differenza delle classi astratte, un'interfaccia è un gruppo completamente astratto di membri che può essere considerato come la definizione di un contratto: chi implementa una interfaccia si impegna a scrivere il codice per ciascun metodo.
Questo significa, in primo luogo, che tutti i metodi e le proprietà definite all'interno di un'interfaccia sono implicitamente astratti, ovvero non è possibile fornirne l'implementazione all'interno dell'interfaccia stessa. Per creare un'interfaccia, si utilizza la parola chiave «interface»:
public interface IEsempio { void Metodo1(); bool Metodo2(); string Proprieta1 { get; set; } int Proprieta2 { get; } }
In questo esempio abbiamo seguito la convenzione secondo cui i nomi delle interfacce dovrebbe iniziare con la lettera I maiuscola. Tutto ciò che è dichiarato all'interno di una interfaccia è pubblico (ed è un errore indicare il modificatore public
).
Un'interfaccia non può contenere variabili, struct, enum e, come già accennato, implementazioni di metodi (tutte cose che, invece, sono consentite nelle classi astratte).
Un'interfaccia viene implementata da una classe. Per indicare che una classe implementa un'interfaccia, in C# si usa il carattere ":", lo stesso con cui si esprime l'ereditarietà tra classi (in VB.NET si usa la parola chiave Implements):
public class Esempio : IEsempio { public void Metodo1() { //... } public bool Metodo2() { return true; } public string Proprieta1 { get { return string.Empty; } set { /*...*/ } } public int Proprieta2 { get { return 0; } } }
Come avviene per le classi astratte, la classe che implementa un'interfaccia deve fornire l'implementazione di tutti i suoi metodi e delle sue proprietà, in questo caso però non c'è bisogno della parola chiave override, è sufficiente che i metodi definiti abbiano lo stesso nome con cui sono dichiarati nell'interfaccia.
In VB.NET, invece, i nomi assegnati ai metodi non sono vincolanti, poiché il metodo dell'interfaccia che si sta implementando è indicato esplicitamente.
Come già visto in precedenza, una classe può ereditare da una sola classe base, mentre può implementare un numero illimitato di interfacce: in questo caso, è necessario definire i metodi e le proprietà di tutte le interfacce:
public class Studente : Persona, IEsempio1, IEsempio2
Questa dichiarazione indica che la classe Studente
estende Persona
e implementa IEsempio1
ed IEsempio2
.
Supponiamo che IEsempio1
ed IEsempio2
contengano un metodo con lo stesso nome, ad esempio un metodo void Calcola()
. La classe Studente
deve definirli entrambi: per evitare conflitti, i nomi dei metodi devono essere preceduti dal nome dall'interfaccia in cui sono definiti:
public void IEsempio1.Calcola() { } public void IEsempio2.Calcola() { }
Un'interfaccia può ereditarne un'altra: in questo caso, la classe che implementa l'interfaccia ereditata deve implementare non solo tutti i suoi metodi e proprietà, ma anche quelli dell'interfaccia base.
Un oggetto che implementa un'interfaccia, inoltre, può essere utilizzato in tutte le parti del codice in cui è richiesta tale interfaccia, quindi anche le interfacce definiscono una nozione di ereditarietà.
Sia le interfacce sia le classi astratte servono per definire una serie di funzionalità di base che devono essere realizzate dalle classi che le ereditano o le implementano.
La scelta di utilizzare un'interfaccia oppure una classe astratta può a volte risultare difficile. MSDN consiglia:
«Mediante le classi astratte possono essere forniti anche membri che sono già stati implementati. Pertanto, con una classe astratta è possibile garantire una certa quantità di funzionalità identiche, a differenza di quanto avviene con un'interfaccia (che non può contenere alcuna implementazione)[...].
Se le funzionalità da creare saranno utilizzate in una vasta gamma di oggetti diversi, usare un'interfaccia. Le classi astratte devono essere usate principalmente per gli oggetti strettamente correlati, mentre le interfacce sono più adatte a fornire funzionalità comuni a classi non correlate».