Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Tips/Råd
Är du nybörjare på att utveckla programvara? Börja med självstudierna Komma igång först. Du kommer att stöta på gränssnitt när du behöver definiera delat beteende mellan orelaterade typer.
Har du erfarenhet av ett annat språk? C#-gränssnitt liknar gränssnitt i Java eller protokoll i Swift. Skumma det explicita implementeringsavsnittet för C#-specifika mönster.
Ett gränssnitt definierar ett kontrakt: en grupp med relaterade metoder, egenskaper, händelser och indexerare som en class eller struct måste implementera. Med gränssnitt kan en enskild typ implementera flera kontrakt, vilket är viktigt eftersom C# inte stöder flera arv av klasser. Structs kan inte ärva från andra structs eller klasser, så gränssnitt är det enda sättet att lägga till delat beteende mellan structtyper.
I följande exempel deklareras ett gränssnitt och en klass som implementerar det:
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);
}
Alla klasser eller strukturer som implementeras IEquatable<T> måste tillhandahålla en Equals metod som matchar gränssnittssignaturen. Du kan räkna med vilken implementering som helst IEquatable<T> för att stödja likhetsjämförelser, oavsett konkret typ. Den förutsägbarheten är kärnvärdet för gränssnitt.
Deklarera ett gränssnitt
Definiera ett gränssnitt med nyckelordet interface . Enligt konventionen börjar gränssnittsnamnen med versaler I:
interface ILogger
{
void Log(string message);
string Name { get; }
}
Gränssnitt kan innehålla metoder, egenskaper, händelser och indexerare. Ett gränssnitt får inte innehålla instansfält, instanskonstruktorer eller slutförare. Medlemmar är public som standard. Du kan ange andra hjälpmedelsmodifierare när det behövs. Använd till exempel internal för medlemmar som inte ska vara synliga utanför sammansättningen.
Implementera ett gränssnitt
En klass eller struct visar de gränssnitt som implementeras efter ett kolon i deklarationen. Klassen måste tillhandahålla en implementering för varje medlem som deklareras i gränssnittet:
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}");
}
}
En klass kan implementera flera gränssnitt, avgränsade med kommatecken. Den måste tillhandahålla implementeringar för alla medlemmar från varje gränssnitt som den listar.
Explicit implementering
Ibland behöver du implementera en gränssnittsmedlem utan att göra den till en del av klassens offentliga API. Explicit implementering kvalificerar medlemmen med gränssnittsnamnet. Medlemmen är endast tillgänglig via en variabel av gränssnittstypen:
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;
}
Explicit implementering är användbart när två gränssnitt deklarerar medlemmar med samma namn eller när du vill hålla klassens offentliga yta ren. Mer information finns i Explicit gränssnittsimplementering.
Gränssnitts-arv
Gränssnitt kan ärva från ett eller flera andra gränssnitt. En klass som implementerar ett härlett gränssnitt måste implementera alla medlemmar från det härledda gränssnittet och alla dess basgränssnitt:
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}");
}
En klass som implementerar IShape kan implicit konverteras till IDrawable, eftersom IShape ärver från den.
Gränssnitt jämfört med abstrakta klasser
Både gränssnitt och abstrakta klasser definierar kontrakt som härledda typer måste uppfylla.
- Använd en abstrakt klass när relaterade typer delar tillstånd (fält), konstruktorer eller icke-offentliga medlemmar. Med abstrakta klasser kan du utveckla en hierarki genom att lägga till nya medlemmar med standardbeteende utan att bryta befintliga härledda typer.
- Använd ett gränssnitt när en typ behöver uppfylla ett kontrakt som skär över orelaterade hierarkier eller när det behöver implementera flera kontrakt. Gränssnitt kan inte deklarera instansfält eller konstruktorer, så de passar bäst för att lägga till funktioner till typer som redan har en basklass. För avancerade scenarier stöder gränssnitt även standardimplementeringar för medlemmar.
En klass kan bara ärva från en basklass men kan implementera flera gränssnitt. Den skillnaden gör ofta gränssnitt till det bättre valet för att definiera funktioner som skär över typhierarkier.
Arbeta med interna gränssnitt
Du kan vanligtvis implementera ett internt gränssnitt med offentliga medlemmar, så länge alla typer i gränssnittssignaturen är offentligt tillgängliga. När ett gränssnitt använder interna typer i sina medlemssignaturer måste du använda explicit implementering eftersom implementeringsmedlemmen inte kan vara offentlig när den exponerar interna typer:
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}");
}
I föregående exempel IConfigurable använder en intern typ InternalConfiguration i sin metodsignatur.
ServiceImplementation använder explicit implementering för den medlemmen. Däremot ILoggable använder endast offentliga typer (string) i sin signatur och kan implementeras implicit.
Standardgränssnittsmedlemmar och statiska abstrakta medlemmar
Gränssnitt stöder två avancerade funktioner som går utöver grundläggande kontrakt:
- Standardgränssnittsmedlemmar låter ett gränssnitt tillhandahålla en metodtext. Implementeringstyper ärver standardimplementeringen och kan eventuellt åsidosätta den. Mer information finns i standardgränssnittsmetoder.
- Statiska abstrakta medlemmar kräver implementeringstyper för att tillhandahålla en statisk medlem, vilket är användbart för att definiera operatorkontrakt eller fabriksmönster. Mer information finns i statiska abstrakta medlemmar i gränssnitt.
Båda funktionerna beskrivs i artikeln om gränssnitt i språkreferensen. De flesta dagliga gränssnittsanvändningen omfattar de deklarerande och implementerande mönster som beskrivs tidigare i den här artikeln.
Sammanfattning av gränssnitt
- Ett gränssnitt definierar ett kontrakt med metoder, egenskaper, händelser och indexerare.
- En klass eller struct som implementerar ett gränssnitt måste tillhandahålla implementeringar för alla deklarerade medlemmar (såvida inte gränssnittet tillhandahåller en standardimplementering).
- Du kan inte instansiera ett gränssnitt direkt.
- En klass eller struct kan implementera flera gränssnitt. En klass kan ärva en basklass och även implementera ett eller flera gränssnitt.
- Gränssnittsnamn börjar konventionellt med
I.