Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wskazówka
Dopiero zaczynasz programować oprogramowanie? Najpierw zacznij od samouczków Wprowadzenie . Napotkasz interfejsy, gdy będziesz musiał zdefiniować współdzielone zachowanie dla niepowiązanych typów.
Czy masz doświadczenie w pracy w innym języku? Interfejsy języka C# są podobne do interfejsów w Java lub protokołach w języku Swift. Przejrzyj sekcję jawnej implementacji w poszukiwaniu wzorców specyficznych dla C#.
Interfejs definiuje kontrakt: grupę powiązanych metod, właściwości, zdarzeń i indeksatorów, które muszą zostać zaimplementowane przez class lub struct. Interfejsy umożliwiają jednemu typowi implementację wielu kontraktów, co jest ważne, ponieważ język C# nie obsługuje wielodziedziczenia klas. Struktury nie mogą dziedziczyć z innych struktur lub klas, więc interfejsy są jedynym sposobem dodawania współużytkowanego zachowania między typami struktur.
Poniższy przykład deklaruje interfejs i klasę, która ją implementuje:
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);
}
Każda klasa lub struktura, która implementuje IEquatable<T> , musi dostarczyć metodę zgodną z sygnaturą Equals interfejsu. Możesz liczyć na dowolną IEquatable<T> implementację do obsługi porównania równości, niezależnie od konkretnego typu. Ta przewidywalność jest główną wartością interfejsów.
Deklarowanie interfejsu
Zdefiniuj interfejs za pomocą słowa kluczowego interface . Zgodnie z konwencją nazwy interfejsów zaczynają się literą I:
interface ILogger
{
void Log(string message);
string Name { get; }
}
Interfejsy mogą zawierać metody, właściwości, zdarzenia i indeksatory. Interfejs nie może zawierać pól wystąpień, konstruktorów wystąpień ani finalizatorów. Członkowie są public domyślnie. W razie potrzeby można określić inne modyfikatory ułatwień dostępu. Na przykład należy użyć elementu internal dla członków, którzy nie powinni być widoczni poza assembly.
Implementowanie interfejsu
Klasa lub struktura wyświetla listę interfejsów, które implementuje po dwukropku w jego deklaracji. Klasa musi zapewnić implementację dla każdego członka zadeklarowanego w interfejsie.
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}");
}
}
Klasa może implementować wiele interfejsów rozdzielonych przecinkami. Musi zapewnić implementacje dla wszystkich członków z każdego interfejsu, które są wymienione.
Implementacja jawna
Czasami należy zaimplementować składową interfejsu bez czynienia jej częścią publicznego interfejsu API klasy. Jawna implementacja kwalifikuje członka nazwą interfejsu. Członek jest dostępny tylko za pośrednictwem zmiennej typu interfejsu:
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;
}
Jawna implementacja jest przydatna, gdy dwa interfejsy deklarują członków o tej samej nazwie lub gdy chcesz zachować czysty publiczny interfejs klasy. Aby uzyskać więcej szczegółów, zobacz Jawna implementacja interfejsu.
Dziedziczenie interfejsu
Interfejsy mogą dziedziczyć z co najmniej jednego innego interfejsu. Klasa, która implementuje interfejs pochodny, musi implementować wszystkie elementy członkowskie z interfejsu pochodnego i wszystkich jego interfejsów podstawowych:
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}");
}
Klasa, która implementuje IShape, może być niejawnie konwertowana na IDrawable, ponieważ IShape dziedziczy po niej.
Interfejsy a klasy abstrakcyjne
Zarówno interfejsy, jak i klasy abstrakcyjne definiują kontrakty, które muszą spełniać typy pochodne.
- Użyj klasy abstrakcyjnej , gdy powiązane typy współdzielą stan (pola), konstruktory lub elementy członkowskie inne niż publiczne. Klasy abstrakcyjne umożliwiają rozwijanie hierarchii przez dodanie nowych elementów członkowskich z zachowaniem domyślnym bez przerywania istniejących typów pochodnych.
- Użyj interfejsu , gdy typ musi spełnić kontrakt, który przecina niepowiązane hierarchie lub kiedy musi implementować wiele kontraktów. Interfejsy nie mogą deklarować pól lub konstruktorów wystąpień, dlatego najlepiej nadają się do dodawania możliwości do typów, które mają już klasę bazową. W przypadku zaawansowanych scenariuszy interfejsy obsługują również domyślne implementacje członków.
Klasa może dziedziczyć tylko jedną klasę bazową, ale może implementować wiele interfejsów. To rozróżnienie często sprawia, że interfejsy są lepszym wyborem do definiowania możliwości, które przecinają hierarchie typów.
Praca z interfejsami wewnętrznymi
Zazwyczaj można zaimplementować wewnętrzny interfejs ze składowymi publicznymi, o ile wszystkie typy w sygnaturze interfejsu są publicznie dostępne. Gdy interfejs używa typów wewnętrznych w podpisach składowych, należy użyć jawnej implementacji, ponieważ element członkowski implementacji nie może być publiczny podczas eksponowania typów wewnętrznych.
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}");
}
W poprzednim przykładzie IConfigurable użyto wewnętrznego typu InternalConfiguration w podpisie metody.
ServiceImplementation używa jawnej implementacji dla tego członka.
ILoggable W przeciwieństwie używa tylko typów publicznych (string) w swojej sygnaturze i można go zaimplementować niejawnie.
Domyślni członkowie interfejsu i statyczni członkowie abstrakcyjni
Interfejsy obsługują dwie zaawansowane funkcje wykraczające poza podstawowe kontrakty:
- Domyślne elementy członkowskie interfejsu umożliwiają interfejsowi podanie treści metody. Implementujące typy dziedziczą domyślną implementację i opcjonalnie mogą ją nadpisać. Aby uzyskać więcej informacji, zobacz domyślne metody interfejsu.
- Statyczne abstrakcyjne członkowie wymagają typów implementujących, aby dostarczyć statycznego członka, co jest przydatne do definiowania kontraktów operatorów lub wzorców fabrycznych. Aby uzyskać więcej informacji, zobacz statyczni abstrakcyjni członkowie w interfejsach.
Obie funkcje zostały omówione w artykule dotyczącym interfejsów w dokumentacji językowej. Większość codziennego użycia interfejsu obejmuje deklarowanie i implementowanie wzorców opisanych wcześniej w tym artykule.
Podsumowanie interfejsów
- Interfejs definiuje kontrakt metod, właściwości, zdarzeń i indeksatorów.
- Klasa lub struktura, która implementuje interfejs, musi zapewnić implementacje dla wszystkich zadeklarowanych elementów członkowskich (chyba że interfejs zapewnia domyślną implementację).
- Nie da się utworzyć wystąpienia interfejsu bezpośrednio.
- Klasa lub struktura może implementować wiele interfejsów. Klasa może dziedziczyć klasę bazową, a także implementować jeden lub więcej interfejsów.
- Nazwy interfejsów zwykle zaczynają się od
I.