Rozhraní – definování chování pro více typů

Návod

Začínáte s vývojem softwaru? Začněte nejprve kurzy Začínáme . S rozhraními se setkáte, když budete potřebovat definovat sdílené chování napříč nesouvisejícími typy.

Máte zkušenosti v jiném jazyce? Rozhraní jazyka C# se podobají rozhraním v Java nebo protokolech ve Swiftu. Přeskočte část explicitní implementace pro vzory specifické pro jazyk C#.

Rozhraní definuje kontrakt: skupinu souvisejících metod, vlastností, událostí a indexerů, které musí implementovat class nebo struct. Rozhraní umožňují implementovat jeden typ více kontraktů, což je důležité, protože jazyk C# nepodporuje více dědičnosti tříd. Struktury nemohou dědit z jiných struktur nebo tříd, takže rozhraní představují jediný způsob, jak přidat sdílené chování napříč typy struktur.

Následující příklad deklaruje rozhraní a třídu, která ji 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);
}

Všechny třídy nebo struktury, které implementují IEquatable<T> , musí poskytovat metodu Equals , která odpovídá podpisu rozhraní. Můžete se spolehnout na jakoukoli IEquatable<T> implementaci, která podporuje porovnání rovnosti bez ohledu na konkrétní typ. Tato předvídatelnost je základní hodnotou rozhraní.

Deklarace rozhraní

Definujte rozhraní s klíčovým slovem interface . Podle konvence začínají názvy rozhraní velkým písmenem I:

interface ILogger
{
    void Log(string message);
    string Name { get; }
}

Rozhraní mohou obsahovat metody, vlastnosti, události a indexery. Rozhraní nemůže obsahovat pole instance, konstruktory instancí ani finalizační metody. Členové jsou public ve výchozím nastavení. V případě potřeby můžete zadat další modifikátory přístupnosti. Například použijte internal pro členy, které by neměly být viditelné mimo sestavení.

Implementace rozhraní

Třída nebo struktura uvádí rozhraní, která implementuje, za použitím dvojtečky ve své deklaraci. Třída musí poskytovat implementaci pro každého člena deklarovaného v rozhraní:

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}");
    }
}

Třída může implementovat více rozhraní oddělených čárkami. Musí poskytovat implementace pro všechny členy z každého rozhraní, které uvádí.

Explicitní implementace

Někdy potřebujete implementovat člen rozhraní bez toho, aby byl součástí veřejného rozhraní API třídy. Explicitní implementace kvalifikuje člena názvem rozhraní. Člen je přístupný pouze prostřednictvím proměnné typu rozhraní:

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;
}

Explicitní implementace je užitečná, když dvě rozhraní deklarují členy se stejným názvem nebo když chcete zachovat veřejný povrch třídy čistý. Další podrobnosti naleznete v tématu explicitní implementace rozhraní.

Dědičnost rozhraní

Rozhraní mohou dědit z jednoho nebo více jiných rozhraní. Třída, která implementuje odvozené rozhraní, musí implementovat všechny členy z odvozeného rozhraní a všechny jeho základní rozhraní:

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}");
}

Třída, která implementuje IShape lze implicitně převést na IDrawable, protože IShape dědí z něj.

Rozhraní vs. abstraktní třídy

Rozhraní i abstraktní třídy definují kontrakty, které musí odvozené typy splnit.

  • Abstraktní třídu použijte , pokud související typy sdílejí stav (pole), konstruktory nebo neveřejné členy. Abstraktní třídy umožňují vyvíjet hierarchii přidáním nových členů s výchozím chováním bez přerušení existujících odvozených typů.
  • Rozhraní použijte, když typ musí naplnit kontrakt, který prolíná nesouvisející hierarchie nebo když musí implementovat více kontraktů. Rozhraní nemohou deklarovat pole instance nebo konstruktory, takže jsou nejvhodnější pro přidání schopností do typů, které už mají základní třídu. V případě pokročilých scénářů rozhraní také podporují výchozí členské implementace.

Třída může dědit pouze z jedné základní třídy, ale může implementovat více rozhraní. Díky tomuto rozdílu je rozhraní lepší volbou pro definování schopností, které se přesoují mezi hierarchiemi typů.

Práce s interními rozhraními

Obvykle můžete implementovat interní rozhraní s veřejnými členy, pokud jsou veřejně přístupné všechny typy v podpisu rozhraní. Pokud rozhraní používá interní typy v podpisech členů, musíte použít explicitní implementaci, protože implementující člen nemůže být veřejný při zveřejnění interních typů:

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}");
}

V předchozím příkladu IConfigurable používá interní typ InternalConfiguration v podpisu metody. ServiceImplementation používá pro tohoto člena explicitní implementaci. Naproti tomu ILoggable ve svém podpisu používá pouze veřejné typy (string) a lze je implementovat implicitně.

Výchozí členy rozhraní a statické abstraktní členy

Rozhraní podporují dvě pokročilé funkce, které překračují základní kontrakty:

  • Výchozí členy rozhraní umožňují rozhraní poskytnout tělo metody. Typy implementací dědí výchozí implementaci a mohou ji volitelně přepsat. Další informace najdete ve výchozích metodách rozhraní.
  • Statické abstraktní členy vyžadují implementaci typů pro poskytnutí statického členu, což je užitečné pro definování kontraktů operátorů nebo vzorů továrny. Další informace najdete v tématu statické abstraktní členy v rozhraních.

Obě funkce jsou popsané v článku o rozhraních v referenční dokumentaci jazyka. Většina každodenního používání rozhraní zahrnuje deklarování a implementaci vzorů popsaných výše v tomto článku.

Souhrn rozhraní

  • Rozhraní definuje kontrakt metod, vlastností, událostí a indexerů.
  • Třída nebo struktura, která implementuje rozhraní, musí poskytovat implementace pro všechny deklarované členy (pokud rozhraní neposkytuje výchozí implementaci).
  • Nelze vytvořit instanci rozhraní přímo.
  • Třída nebo struktura může implementovat více rozhraní. Třída může dědit základní třídu a také implementovat jedno nebo více rozhraní.
  • Názvy rozhraní obvykle začínají na I.