A C# a 8.0-ás változattól lehetőséget biztosít arra, hogy az interfészek implementációt is tartalmazzanak.
A szoftverfejlesztés nagy akadálya, hogy nem vehetünk fel új tagokat egy nyilvános interfészre következmények nélkül. Ha egy interfészt kibővítünk, akkor megtörjük a interfész meglévő megvalósítóival a kompatibilitást, mivel az újonnan bevezetett interfész tagnak nem lenne megvalósítása a meglévő implementációkban.
Az alapértelmezett megvalósítások segítenek ebben. Az interfész új tagjait meg lehet adni kóddal együtt és ha egy implementációs osztály, rekord vagy struktúra nem biztosítja az adott tag megvalósítását, akkor nem keletkezik fordítási hiba, mivel az alapértelmezett megvalósítás kerül használatra.
Tételezzük fel, hogy az alkalmazásunk publikálja a következő interfészt, amit más modulok felhasználhatnak:
enum LogLevel
{
Info,
Warning,
Error,
}
interface ILogger
{
void Log(LogLevel level, string message);
}
Az interfész egyik ilyen megvalósítása pedig a következő:
class ConsoleLogger : ILogger
{
public void Log(LogLevel level, string message)
{
Console.WriteLine("{0}: {1}", level, message);
}
}
A fenti megvalósítás működik a programunkban, de idő közben rájöttünk, hogy az interfész használata problémás, mivel ha egy kivételt szeretnénk naplózni, akkor a fejlesztőre bízzuk a kivétel prioritását, ami sosem jó. Éppen ezért bővíteni szeretnénk egy kivétel naplózó metódussal.
Ebben az esetben, ha nem C# 8.0 vagy újabb verziót használunk, akkor az interfész módosítása után az összes naplózó megoldásunkat módosítanunk kell, hogy kompatibilisek maradjanak az új interfészünkkel.
C# 8.0 vagy újabb esetén viszont megtehetjük a következőt:
interface ILogger
{
void Log(LogLevel level, string message);
void Log(Exception ex) => Log(LogLevel.Error, ex.ToString());
}
A módosítás után a ConsoleLogger továbbra is teljesíti az interfész által biztosított szerződést. Ha a ConsoleLogger osztályt ILogger interfészre kasztoljuk és az új, kivételt naplózó metódust hívjuk meg, akkor az interfészben található alapértelmezett megvalósítás fog lefutni.
//egy metódus, ami kivételt dobhat
void KiveteltDobhat()
{
throw new NotImplementedException();
}
//ILogger interfészre alakítás
ILogger log = new ConsoleLogger();
try
{
KiveteltDobhat()
}
catch (Exception ex)
{
//alepértelmezett implementáció hívása
log.Log(ex);
}
Ez a nyelvi szolgáltatás lehetővé teszi az API-szerzők számára, hogy metódusokat adjanak egy interfészhez későbbi verziókban anélkül, hogy megszakítanák a forrás vagy bináris kompatibilitást az adott interfész meglévő megvalósításával.
További előny még, hogy ezzel az eljárással elérhető a trait jellegű programozás, ahol az osztályokat különböző interfészekből jövő funkciókkal tudjuk egyszerűen felvértezni.
Mivel a hívásnál meg kell határoznunk a konkrét típust, így a többszörös örökléssel ellentétben itt továbbra sincs jelen az úgynevezett gyémánt probléma. Ezzel a nyelvi újdonsággal némileg csökkent a különbség az interfészek és az absztrakt osztályok között, de az interfészek továbbra sem tartalmazhatnak nem-publikus adattagokat és az osztályok továbbra is csak egy absztrakt közvetlen őst bővíthetnek ki, míg több interfészt is megvalósíthatnak.