Interfacce: definire il comportamento per più tipi

Un'interfaccia contiene definizioni per un gruppo di funzionalità correlate che devono essere implementate da un class o un struct non astratto. Un'interfaccia può definire static metodi che devono avere un'implementazione. Un'interfaccia può definire un'implementazione predefinita per i membri. Un'interfaccia potrebbe non dichiarare dati di istanza, ad esempio campi, proprietà implementate automaticamente o eventi simili a proprietà.

Usando le interfacce, è possibile, ad esempio, includere il comportamento di più origini in una classe. Tale funzionalità è importante in C# perché il linguaggio non supporta l'ereditarietà multipla delle classi. Inoltre è necessario usare un'interfaccia se si vuole simulare l'ereditarietà per le struct, perché non possono effettivamente ereditare da un'altra struct o classe.

Per definire un'interfaccia, si usa la parola chiave interface come illustrato nell'esempio seguente.

interface IEquatable<T>
{
    bool Equals(T obj);
}

Il nome di un'interfaccia deve essere un nome di identificatore C# valido. Per convenzione, i nomi di interfaccia iniziano con una lettera I maiuscola.

Qualsiasi classe o struct che implementa l'interfaccia IEquatable<T> deve contenere una definizione per un metodo Equals che corrisponde alla firma specificata dall'interfaccia. Di conseguenza, è possibile affidarsi a una classe di tipo T che implementa IEquatable<T> per contenere un metodo Equals con cui un'istanza della classe può determinare se sia uguale a un'altra istanza della stessa classe.

La definizione di IEquatable<T> non fornisce un'implementazione per Equals. Una classe o uno struct può implementare più interfacce, ma una classe può ereditare solo da una singola classe.

Per altre informazioni sulle classi astratte, vedere Classi e membri delle classi astratte e sealed.

Le interfacce possono contenere metodi di istanza, proprietà, eventi, indicizzatori o qualsiasi combinazione di questi quattro membri. Le interfacce possono contenere costruttori statici, campi, costanti o operatori. A partire da C# 11, i membri dell'interfaccia che non sono campi possono essere static abstract. Un'interfaccia non può contenere campi di istanza, costruttori di istanze o finalizzatori. I membri dell'interfaccia sono pubblici per impostazione predefinita ed è possibile specificare in modo esplicito i modificatori di accessibilità, ad esempio public, protected, internal, private, protected internal o private protected. Un membro private deve avere un'implementazione predefinita.

Per implementare un membro di interfaccia, il corrispondente membro della classe di implementazione deve essere pubblico e non statico e avere lo stesso nome e la stessa firma del membro di interfaccia.

Nota

Quando un'interfaccia dichiara membri statici, un tipo che implementa tale interfaccia può anche dichiarare membri statici con la stessa firma. Sono distinte e identificate in modo univoco dal tipo che dichiara il membro. Il membro statico dichiarato in un tipo non esegue l'override del membro statico dichiarato nell'interfaccia.

Una classe o uno struct che implementa un'interfaccia deve fornire un'implementazione per tutti i membri dichiarati senza un'implementazione predefinita fornita dall'interfaccia. Tuttavia, se una classe base implementa un'interfaccia, qualsiasi classe derivata dalla classe base eredita tale implementazione.

Nell'esempio seguente viene illustrata un'implementazione dell'interfaccia IEquatable<T>. La classe di implementazione, Car, deve fornire un'implementazione del metodo Equals.

public class Car : IEquatable<Car>
{
    public string? Make { get; set; }
    public string? Model { get; set; }
    public string? Year { get; set; }

    // Implementation of IEquatable<T> interface
    public bool Equals(Car? car)
    {
        return (this.Make, this.Model, this.Year) ==
            (car?.Make, car?.Model, car?.Year);
    }
}

Le proprietà e gli indicizzatori di una classe possono definire altre funzioni di accesso per una proprietà o un indicizzatore definito in un'interfaccia. Ad esempio, un'interfaccia può dichiarare una proprietà con una funzione di accesso get. La classe che implementa l'interfaccia può dichiarare la stessa proprietà con una funzione di accesso get o set. Tuttavia, se la proprietà o l'indicizzatore usa l'implementazione esplicita, le funzioni di accesso devono corrispondere. Per altre informazioni sull'implementazione esplicita, vedere Implementazione esplicita dell'interfaccia e Proprietà dell'interfaccia.

Le interfacce possono ereditare da una o più interfacce. L'interfaccia derivata eredita i membri dalle relative interfacce di base. Una classe che implementa un'interfaccia derivata deve implementare tutti i membri nell'interfaccia derivata, inclusi tutti i membri delle interfacce di base dell'interfaccia derivata. Tale classe può essere convertita in modo implicito nell'interfaccia derivata o in una delle relative interfacce di base. Una classe può includere un'interfaccia più volte tramite le classi di base ereditate o tramite le interfacce ereditate da altre interfacce. Tuttavia, la classe può fornire un'implementazione di un'interfaccia solo una volta e solo se la classe dichiara l'interfaccia durante la definizione della classe (class ClassName : InterfaceName). Se l'interfaccia viene ereditata perché è stata ereditata una classe base che implementa l'interfaccia, la classe base fornisce l'implementazione dei membri dell'interfaccia. Tuttavia, la classe derivata può reimplementare qualsiasi membro dell'interfaccia invece di usare l'implementazione ereditata. Quando le interfacce dichiarano un'implementazione predefinita di un metodo, qualsiasi classe che implementa tale interfaccia eredita tale implementazione (è necessario eseguire il cast dell'istanza della classe al tipo di interfaccia per accedere all'implementazione predefinita nel membro di interfaccia).

Una classe base può implementare anche i membri di interfaccia usando membri virtuali. In tal caso, una classe derivata può modificare il comportamento dell'interfaccia eseguendo l'override dei membri virtuali. Per altre informazioni su membri virtuali, vedere Polimorfismo.

Riepilogo delle interfacce

Un'interfaccia presenta le proprietà seguenti:

  • Nelle versioni C# precedenti alla 8.0, un'interfaccia è simile a una classe base astratta con solo membri astratti. Una classe o uno struct che implementa l'interfaccia deve implementarne tutti i membri.
  • A partire da C# 8.0, un'interfaccia può definire implementazioni predefinite per alcuni o tutti i relativi membri. Una classe o uno struct che implementa l'interfaccia non deve implementare membri con implementazioni predefinite. Per ulteriori informazioni, consultare Metodi di interfaccia predefiniti.
  • Non è possibile creare direttamente un'istanza di un'interfaccia. I membri vengono implementati da qualsiasi classe o struct che implementa l'interfaccia.
  • Una classe o struct può implementare più interfacce. Una classe può ereditare una classe base e anche implementare una o più interfacce.