Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Suggerimento
Novità dello sviluppo di software? Iniziare prima con le esercitazioni introduttive . Dopo aver definito il comportamento condiviso tra tipi non correlati, si incontreranno interfacce.
Esperienza in un'altra lingua? Le interfacce C# sono simili alle interfacce in Java o protocolli in Swift. Sfogliare la sezione di implementazione esplicita in cerca di modelli specifici di C#.
Un'interfaccia definisce un contratto: un gruppo di metodi, proprietà, eventi e indicizzatori correlati che un class oggetto o struct deve implementare. Le interfacce consentono a un singolo tipo di implementare più contratti, che è importante perché C# non supporta più ereditarietà delle classi. Gli struct non possono ereditare da altri struct o classi, quindi le interfacce sono l'unico modo per aggiungere il comportamento condiviso tra tipi di struct.
L'esempio seguente dichiara un'interfaccia e una classe che la implementa:
interface IEquatable<T>
{
bool Equals(T obj);
}
public class Car : IEquatable<Car>
{
public string? Make { get; set; }
public string? Model { get; set; }
public string? Year { get; set; }
public bool Equals(Car? car) =>
car is not null &&
(Make, Model, Year) == (car.Make, car.Model, car.Year);
}
Qualsiasi classe o struct che implementa IEquatable<T> deve fornire un Equals metodo che corrisponda alla firma dell'interfaccia. È possibile contare su qualsiasi IEquatable<T> implementazione per supportare il confronto di uguaglianza, indipendentemente dal tipo concreto. Tale prevedibilità è il valore principale delle interfacce.
Dichiarare un'interfaccia
Definire un'interfaccia con la interface parola chiave . Per convenzione, i nomi di interfaccia iniziano con un maiuscolo I:
interface ILogger
{
void Log(string message);
string Name { get; }
}
Le interfacce possono contenere metodi, proprietà, eventi e indicizzatori. Un'interfaccia non può contenere campi di istanza, costruttori di istanze o finalizzatori. I membri sono public per impostazione predefinita. È possibile specificare altri modificatori di accessibilità quando necessario. Ad esempio, usare internal per i membri che non dovrebbero essere visibili all'esterno dell'assemblaggio.
Implementare un'interfaccia
Una classe o uno struct elenca le interfacce implementate dopo i due punti nella relativa dichiarazione. La classe deve fornire un'implementazione per ogni membro dichiarato nell'interfaccia :
public class ConsoleLogger : ILogger
{
public string Name => "Console";
public void Log(string message) =>
Console.WriteLine($"[{Name}] {message}");
}
public class FileLogger : ILogger
{
public string Name => "File";
public void Log(string message)
{
// In a real app, write to a file
Console.WriteLine($"[{Name}] Writing to file: {message}");
}
}
Una classe può implementare più interfacce, separate da virgole. Deve fornire implementazioni per tutti i membri di ogni interfaccia elencata.
Implementazione esplicita
A volte è necessario implementare un membro dell'interfaccia senza renderlo parte dell'API pubblica della classe. L'implementazione esplicita identifica il membro con il nome dell'interfaccia. Il membro è accessibile solo tramite una variabile del tipo di interfaccia:
interface IMetric
{
double GetDistance(); // Returns meters
}
interface IImperial
{
double GetDistance(); // Returns feet
}
public class Runway(double meters) : IMetric, IImperial
{
// Explicit implementation for IMetric
double IMetric.GetDistance() => meters;
// Explicit implementation for IImperial
double IImperial.GetDistance() => meters * 3.28084;
}
L'implementazione esplicita è utile quando due interfacce dichiarano membri con lo stesso nome o quando si vuole mantenere pulita la superficie pubblica della classe. Per altri dettagli, vedere Implementazione esplicita dell'interfaccia.
Ereditarietà dell'interfaccia
Le interfacce possono ereditare da una o più interfacce. Una classe che implementa un'interfaccia derivata deve implementare tutti i membri dall'interfaccia derivata e tutte le relative interfacce di base:
interface IDrawable
{
void Draw();
}
interface IShape : IDrawable
{
double Area { get; }
}
public class Circle(double radius) : IShape
{
public double Area => Math.PI * radius * radius;
public void Draw() =>
Console.WriteLine($"Drawing circle with area {Area:F2}");
}
Una classe che implementa IShape può essere convertita in modo implicito in IDrawable, perché IShape eredita da essa.
Interfacce e classi astratte
Entrambe le interfacce e le classi astratte definiscono contratti che i tipi derivati devono soddisfare.
- Usare una classe astratta quando i tipi correlati condividono lo stato (campi), i costruttori o i membri non pubblici. Le classi astratte consentono di evolvere una gerarchia aggiungendo nuovi membri con comportamento predefinito senza interrompere i tipi derivati esistenti.
- Usare un'interfaccia quando un tipo deve soddisfare un contratto che taglia tra gerarchie non correlate o quando deve implementare più contratti. Le interfacce non possono dichiarare campi o costruttori di istanza, quindi sono più adatte per l'aggiunta di funzionalità ai tipi che hanno già una classe base. Per gli scenari avanzati, le interfacce supportano anche le implementazioni predefinite dei membri.
Una classe può ereditare da una sola classe di base, ma può implementare più interfacce. Questa distinzione spesso rende le interfacce la scelta migliore per definire le funzionalità che si tagliano tra gerarchie di tipi.
Uso delle interfacce interne
In genere è possibile implementare un'interfaccia interna con membri pubblici, purché tutti i tipi nella firma dell'interfaccia siano accessibili pubblicamente. Quando un'interfaccia usa tipi interni nelle firme dei membri, è necessario usare l'implementazione esplicita perché il membro di implementazione non può essere pubblico durante l'esposizione di tipi interni:
internal class InternalConfiguration
{
public string Setting { get; set; } = "";
}
internal interface ILoggable
{
void Log(string message);
}
internal interface IConfigurable
{
void Configure(InternalConfiguration config);
}
public class ServiceImplementation : ILoggable, IConfigurable
{
// Implicit implementation: ILoggable uses only public types in its signature
public void Log(string message) =>
Console.WriteLine($"Log: {message}");
// Explicit implementation: IConfigurable uses internal types
void IConfigurable.Configure(InternalConfiguration config) =>
Console.WriteLine($"Configured with: {config.Setting}");
}
Nell'esempio precedente, IConfigurable usa un tipo InternalConfiguration interno nella firma del metodo.
ServiceImplementation usa l'implementazione esplicita per tale membro. Al contrario, ILoggable usa solo i tipi pubblici (string) nella relativa firma e può essere implementato in modo implicito.
Membri di interfaccia predefiniti e membri astratti statici
Le interfacce supportano due funzionalità avanzate che vanno oltre i contratti di base:
- I membri di interfaccia predefiniti consentono a un'interfaccia di fornire un corpo del metodo. I tipi che implementano ereditano l'implementazione predefinita e possono eseguirne l'override. Per ulteriori informazioni, consultare Metodi di interfaccia predefiniti.
- I membri astratti statici richiedono l'implementazione di tipi per fornire un membro statico, utile per definire i contratti degli operatori o i modelli factory. Per altre informazioni, vedere Membri astratti statici nelle interfacce.
Entrambe le funzionalità sono descritte nell'articolo sulle interfacce nel riferimento al linguaggio. La maggior parte dell'utilizzo quotidiano dell'interfaccia implica la dichiarazione e l'implementazione dei modelli descritti in precedenza in questo articolo.
Riepilogo delle interfacce
- Un'interfaccia definisce un contratto di metodi, proprietà, eventi e indicizzatori.
- Una classe o uno struct che implementa un'interfaccia deve fornire implementazioni per tutti i membri dichiarati (a meno che l'interfaccia non fornisca un'implementazione predefinita).
- Non è possibile creare direttamente un'istanza di un'interfaccia.
- Una classe o struct può implementare più interfacce. Una classe può ereditare una classe base e anche implementare una o più interfacce.
- I nomi di interfaccia iniziano convenzionalmente con
I.