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.