Diamantproblemet i C# 8

with No Comments

I C# kan en klass ärva från max en annan klass, men från hur många interfaces som helst. Den begränsningen skyddar programmeraren från utmaningar som kan uppstå i språk som tillåter multipla arv (t.ex. C++), som t.ex. att två basklasser implementerar samma funktion. Detta kallas för diamantproblemet för att det typiskt uppstår i en diamantrelation, alltså när två klasser ärver från en basklass och var för sig implementerar en metod, och en ny klass som ärver från båda dessa arvsklasser introduceras. Vilken av dessa metoder ska vara tillgänglig i arvsklassen? Bilden visar en diamantrelation, alltså en klass som ärver från två klasser med gemensam basklass.

För interfaces som inte innehåller några implementationer så spelar det ingen roll om en klass ärver från två typer som deklarerar samma funktion. Det betyder bara att arvsklassen säger sig ha den funktionen. Så om typ A, B och C är interfaces och X är en deklaration av en metod, innebär det inget annat än att typ D har en metod som heter X.

I C# är vi vana med att interfaces endast innehåller publika deklarationer, vilket gör att multipla arv inte skapar problemet med diamantrelationer som vi känner från C++. Men i och med version 8 av C# så kan interfaces innehålla både publika deklarationer och implementationer. Detta kallas för default interface members och syftar till att undvika problem som uppstår när en ny medlem skapas på ett interface som redan ärvs ifrån på flera ställen i ett system.

Tidigare, om en ny medlem introduceras i ett interface, kompilerar inte längre programmet förrän alla klasser som ärver från interfacet i fråga har tillhandahållit en implementation av den nya medlemmen. Men nu finns alternativet att låta interfacet självt tillhandahålla en standardimplementation, vilket rent effektivt innebär att C# nu faktiskt kan ärva från fler än en typ som kan innehålla implementationer. Följande kod illustrerar hur klassen Doberman är en korrekt implementation av Dog, trots att den inte definierar metoden Run.

Metoden Run är abstrakt, vilket innebär att om Doberman tillhandahåller en egen implementation av Run, så är det den egna metoden som blir anropad.

Om vi modifierar ursprungsexemplet så att både B och C implementerar X, kommer programmet inte kompilera eftersom man inte kan veta vad ett anrop på X i D är tänkt att betyda.

Alltså, så länge B och C deklarerar förekomsten av X, betyder arvet från B och C blott att X finns, men när även implementationen ärvs, bör man designa sina arv så att man inte springer på diamantproblemet. C# 8 har ett regelverk (most specific override) för vilken metod som blir anropad efter arv från interfaces, men i exemplet ovan har X i B och X i C samma status i D. En enkel lösning som kan användas för att låta D ärva från både B och C är tillhandahålla en egen implementation av X i D, som då blir den som kommer att användas i anrop på X i objekt av typen D.

Follow Anders Hesselbom:

Latest posts from

Leave a Reply