1. Introduzione
Di solito, quando implementi un'interfaccia, semplicemente crei metodi pubblici nella tua classe, le cui firme coincidono con quelle dichiarate nell'interfaccia. Questo si chiama implementazione implicita o implementazione pubblica. Il compilatore è abbastanza smart da capire: "Ok, questo metodo DoSomething() nella classe MyClass serve per implementare IDoable.DoSomething()!".
Ma che succede se:
- La tua classe SmartDevice implementa l'interfaccia ICamera (che ha il metodo TakePicture()) e l'interfaccia IScreen (che ha anch'essa il metodo TakePicture() — per fare uno screenshot)?
- Oppure la tua classe Robot ha già un metodo pubblico Reset() per il reset totale di tutti i sistemi, ma vuoi anche che implementi l'interfaccia IDevice con il metodo Reset(), che dovrebbe resettare solo alcune impostazioni?
In questi casi nasce ambiguità o il desiderio di separare chiaramente le funzionalità. Ed è qui che entra in gioco l'implementazione esplicita dell'interfaccia.
L'implementazione esplicita dell'interfaccia ti permette di dire al compilatore: "Questo metodo specifico serve per implementare proprio questa interfaccia, e sarà accessibile solo tramite un riferimento a questa interfaccia". È come Peter Parker che spara la ragnatela solo quando agisce come Spider-Man, e usa le sue skill da fotografo quando è il "normale" Peter Parker.
2. Quando e perché serve l'implementazione esplicita dell'interfaccia
Quando implementi un'interfaccia nel modo classico, i suoi metodi e proprietà diventano parte dell'interfaccia pubblica della tua classe. Ma a volte vuoi che siano accessibili solo tramite l'interfaccia stessa, e non direttamente dalla classe. Succede più spesso di quanto pensi! Per esempio, se due interfacce richiedono lo stesso metodo (ma con significati diversi), oppure vuoi limitare l'accesso all'implementazione solo a chi lavora con l'oggetto tramite l'interfaccia.
Qui l'implementazione esplicita dell'interfaccia è la tua salvezza. È come un passaggio segreto nell'architettura del software: da fuori non si vede, ma chi sa — passa!
Scenario 1: Conflitto di nomi
Immagina di avere una classe che implementa due interfacce, entrambe richiedono un metodo con lo stesso nome, ma logica completamente diversa. Per esempio:
interface IWriter
{
void Print();
}
interface IPrinter
{
void Print();
}
Vuoi che IWriter.Print() scriva il testo su file, mentre IPrinter.Print() lo mandi alla stampante. Un'implementazione normale del metodo Print() non ti permette di separare i comportamenti. Qui l'implementazione esplicita ti salva la giornata.
Scenario 2: Nascondere metodi tecnici/non rilevanti dell'interfaccia
A volte la tua classe deve implementare un metodo dell'interfaccia, ma non vuoi proprio offrirlo a tutti gli utenti della classe (ad esempio, il membro dell'interfaccia serve solo per uso interno nell'infrastruttura).
Scenario 3: Protezione da chiamate "per errore"
Se il metodo dell'interfaccia non è pensato per essere chiamato direttamente (tipo un meccanismo interno del framework), puoi implementarlo esplicitamente — così nessun programmatore per sbaglio lo chiamerà direttamente tramite l'oggetto della classe.
3. Sintassi dell'implementazione esplicita dell'interfaccia
La differenza principale — con l'implementazione esplicita dai il nome completo del metodo o proprietà con il qualificatore dell'interfaccia. E niente modificatori di accesso (public/private) o la parola chiave override!
Sintassi generale:
Tipo_di_ritorno NomeInterfaccia.NomeMetodo(parametri)
{
// implementazione
}
Sembra quasi che tu stia dicendo chiaramente il nome dell'interfaccia, così il compilatore e i colleghi non si confondono su quale contratto si riferisce questa implementazione.
Risoluzione del conflitto di nomi
Vediamo proprio il caso di IWriter e IPrinter. Continuiamo a sviluppare la nostra app didattica, dove, per esempio, abbiamo una classe report:
interface IWriter
{
void Print();
}
interface IPrinter
{
void Print();
}
public class Report : IWriter, IPrinter
{
// Implementazione esplicita di IWriter.Print
void IWriter.Print()
{
Console.WriteLine("Salviamo il report su file (Writer)...");
}
// Implementazione esplicita di IPrinter.Print
void IPrinter.Print()
{
Console.WriteLine("Mandiamo il report alla stampante (Printer)...");
}
// Metodo aggiuntivo per output utente
public void Show()
{
Console.WriteLine("Mostriamo il report a schermo.");
}
}
Ora proviamo a usare le diverse interfacce:
var report = new Report();
report.Show(); // Metodo pubblico normale
// report.Print(); // Errore! Nessun metodo Print con questo nome nella classe Report
IWriter writer = report;
writer.Print(); // Chiama l'implementazione di IWriter.Print()
IPrinter printer = report;
printer.Print(); // Chiama l'implementazione di IPrinter.Print()
Qui i metodi Print non sono accessibili tramite l'oggetto report, ma solo tramite la giusta interfaccia. Questo è il cuore dell'implementazione esplicita: solo tramite la "porta" dell'interfaccia arrivi al metodo giusto.
Come appare in memoria: semplice illustrazione
In pratica, l'implementazione esplicita "nasconde" il membro dell'interfaccia dentro la classe. Per visualizzarlo, puoi immaginare una tabella così:
| Come accedi | Cosa viene effettivamente chiamato |
|---|---|
|
Errore di compilazione — nessun metodo così |
|
Implementazione esplicita |
|
Implementazione esplicita |
|
Metodo Show della classe |
4. Esempio: Interfaccia — solo per l'infrastruttura
In un progetto reale capita spesso questo scenario: una classe deve implementare un'interfaccia di servizio, ma l'implementazione non serve agli utenti normali della classe.
interface IBroadcastable
{
void Broadcast();
}
public class SecretMessage : IBroadcastable
{
void IBroadcastable.Broadcast()
{
Console.WriteLine("Messaggio segreto mandato in onda...");
}
public void Reveal()
{
Console.WriteLine("Mostriamo il segreto a schermo.");
}
}
// Nel codice normale:
var message = new SecretMessage();
message.Reveal(); // Metodo utente
// message.Broadcast(); // Errore! — nessun metodo così
// Solo l'infrastruttura sa cosa fare:
((IBroadcastable)message).Broadcast();
Qui il metodo Broadcast() — solo per chi lavora tramite il contratto dell'interfaccia.
5. Implementazione esplicita di proprietà e indicizzatori
Non solo i metodi possono essere implementati esplicitamente, ma anche proprietà e persino indicizzatori.
interface IDescribable
{
string Description { get; }
}
public class Product : IDescribable
{
// Implementazione esplicita della proprietà
string IDescribable.Description => "Descrizione disponibile solo tramite interfaccia";
// Proprietà pubblica normale
public string Name { get; set; }
}
// Esempio d'uso:
var p = new Product { Name = "Gadget" };
// p.Description; // Errore! Nessuna proprietà così in Product
var descr = ((IDescribable)p).Description;
Console.WriteLine(descr);
6. Sfumature utili
Come funziona l'ereditarietà con l'implementazione esplicita
Con l'ereditarietà tutto fila liscio: se la classe base implementa l'interfaccia esplicitamente, la classe derivata eredita questo "comportamento". Ma se la derivata vuole ridefinire l'implementazione del metodo dell'interfaccia, non può: l'implementazione esplicita non può essere virtuale. Quindi, non puoi ridefinire un membro implementato esplicitamente.
Se ti serve questo comportamento — devi usare l'implementazione normale o pensare al pattern "template method".
Implementazione esplicita e implicita
+----------------+
| Invoice |
+----------------+
| Show() | // Puoi chiamarlo direttamente
+----------------+
| ITxtExportable.Export() // Solo tramite ITxtExportable
| IJsonExportable.Export() // Solo tramite IJsonExportable
+----------------+
Caratteristiche/limiti dell'implementazione esplicita
- I metodi e le proprietà implementati esplicitamente non possono avere modificatori di accesso. Sono privati per il mondo esterno e accessibili solo tramite l'interfaccia.
- Non puoi farli static, virtual, abstract o override.
- Non puoi accedere a un membro implementato esplicitamente tramite l'oggetto della classe direttamente (solo tramite una variabile di tipo interfaccia).
- Se un'interfaccia eredita da un'altra interfaccia, puoi implementare esplicitamente membri di qualsiasi livello della gerarchia.
7. Vantaggi dell'implementazione esplicita
L'implementazione esplicita non è solo un trucchetto sintattico per risolvere collisioni. Ha diversi motivi validi per esistere:
Risoluzione delle collisioni di nomi (The Big One): È il motivo principale e più ovvio. Se due interfacce che implementi dichiarano metodi (o proprietà) con la stessa firma, l'implementazione esplicita ti permette di fornire implementazioni separate, specifiche per ogni interfaccia. Senza questo avresti ambiguità.
Esempio reale: Immagina di avere una stampante. Può essere anche uno scanner. L'interfaccia IPrinter ha il metodo Print(), l'interfaccia IScanner — il metodo Scan(). Ma se entrambe avessero ProcessDocument()? L'implementazione esplicita ti permette di fare IPrinter.ProcessDocument() per stampare e IScanner.ProcessDocument() per scansionare, e funzionano in modo diverso.
Nascondere dettagli di implementazione e pulizia dell'API della classe: I metodi implementati esplicitamente non fanno parte dell'API pubblica della tua classe. Sono accessibili solo facendo il cast dell'oggetto al tipo interfaccia. Molto utile quando vuoi che una certa funzionalità sia disponibile solo "per contratto", e non come parte del comportamento generale dell'oggetto.
Esempio: Stai creando uno strumento finanziario complesso, tipo CreditCard. Può implementare IPayable (per fare pagamenti) e IAdminConfigurable (per impostazioni interne, tipo limiti). Il metodo IAdminConfigurable.SetLimit() non deve essere accessibile a tutti quelli che hanno in mano una CreditCard. Deve essere accessibile solo al sistema amministrativo che lavora con CreditCard come IAdminConfigurable. L'implementazione esplicita ti permette di tenere SetLimit() nascosto, rendendo l'API della classe più pulita e sicura.
Garanzia del contratto: A volte un metodo nella tua classe per caso ha la stessa firma di un metodo nell'interfaccia, ma non vuoi che venga considerato la sua implementazione. Per esempio, hai una classe MyList con il metodo Clear() per pulire il suo stato interno. Se decidi che MyList deve implementare IList<T>, che ha anch'essa Clear(), di default il tuo MyList.Clear() diventa l'implementazione di IList<T>.Clear(). Se la logica deve essere diversa, l'implementazione esplicita IList<T>.Clear() ti permette di separarle.
Implementazione implicita vs. esplicita
Per capire meglio la differenza, guarda questa tabella comparativa:
| Caratteristica | Implementazione implicita | Implementazione esplicita |
|---|---|---|
| Accessibilità | Accessibile sia tramite la classe che tramite l'interfaccia. | Accessibile solo tramite l'interfaccia. |
| Modificatore di accesso | Di solito public (o protected ecc.). | Nessun modificatore di accesso (non può essere public). |
| Sintassi | |
|
| Risoluzione collisioni | Non risolve, il metodo serve per tutte le interfacce con quella firma. | Risolve le collisioni, permettendo implementazioni diverse. |
| Pulizia API della classe | Il metodo fa parte dell'API pubblica della classe. | Il metodo non fa parte dell'API pubblica della classe. |
| Scopo | Per implementazioni generali e univoche delle interfacce. | Per risolvere collisioni o nascondere logica specifica. |
Come vedi, sono due facce della stessa medaglia, ognuna con il suo scopo. Nella maggior parte dei casi userai l'implementazione implicita, perché è più semplice e comoda. L'implementazione esplicita è uno strumento per casi specifici ma importanti.
8. Errori tipici con l'implementazione esplicita delle interfacce
Errore n°1: il metodo non è visibile tramite l'oggetto della classe.
I metodi implementati esplicitamente non sono accessibili direttamente tramite l'istanza della classe. Spesso nasce confusione: il compilatore dice che il metodo non esiste, anche se c'è — solo che è "nascosto". Soluzione: fai il cast dell'oggetto al tipo interfaccia, tipo: (IMyInterface)obj.Method().
Errore n°2: uso errato dei modificatori di accesso.
Con l'implementazione esplicita non puoi mettere modificatori come public o override. Se ci provi, errore di compilazione — il compilatore non li accetta.
Errore n°3: tentativo di ridefinire un metodo implementato esplicitamente in una classe derivata.
Se il metodo dell'interfaccia è implementato esplicitamente nella classe base, non puoi ridefinirlo nella derivata. Questo limite va considerato quando progetti gerarchie di classi con interfacce.
GO TO FULL VERSION