Discussione:
ereditarietà multipla
(troppo vecchio per rispondere)
Kiuhnm
2009-07-21 21:51:57 UTC
Permalink
Sia IA un'interfaccia:

interface IA
{
int f1();
int f2();
}

Sia B una seconda "versione" dell'interfaccia IA:

abstract class B : IA
{
public abstract int f1();

int IA.f2() { return (int)f2(); }
public abstract char f2();
}

B espone IA.f1, ma ""nasconde"" IA.f2 presentando l'alternativa IB.f2.
L'"interfaccia" B è equivalente a IA a livello d'informazione, quindi
l'utente può limitarsi a implementare la sola B e quest'ultima
provvederà ad implementare IA.
B è, in una parola, retrocompatibile.

La classe User implementa l'"interfaccia" B in questo modo:

class User : B
{
public override int f1() { return 1; }
public override char f2() { return 'a'; }
}

Il risultato è quello che ci si aspetterebbe:
il codice

static void Main()
{
User user = new User();
IA userA = user;
B userB = user;
Console.WriteLine("userA.f1() = {0}", userA.f1());
Console.WriteLine("userA.f2() = {0}", userA.f2());
Console.WriteLine("userB.f2() = {0}", userB.f2());
}

stampa

userA.f1() = 1
userA.f2() = 97
userB.f2() = a

Il problema è che B non è un'interfaccia agli occhi del C# quindi va
implementata come classe. Dato che l'ereditarietà multipla non è
disponibile in C#, questo pattern risulta in pratica inutilizzabile.

Studiando i generics in C# ho notato che IEnumerable<T> deriva da
IEnumerable. Fin qui niente di strano.
La sorpresa è stata scoprire che per implementare IEnumerable<T> devo
fornire l'implementazione di ben due metodi praticamente identici!
Penso che nel 99.9% dei casi uno dei due metodi chiami quell'altro (o
entrambi un metodo d'appoggio). Tra l'altro occorre implementare almeno
uno dei due metodi in modo esplicito.
Non mi sembra una soluzione molto pulita.
La situazione con IEnumerator<T> è analoga.

Mi piacerebbe sapere cosa pensate di questa situazione. Vi è capitato in
ambito reale di sentire la mancanza dell'ereditarietà multipla o no?

Kiuhnm
Raffaele Rialdi [MVP]
2009-07-21 22:23:47 UTC
Permalink
Post by Kiuhnm
Studiando i generics in C# ho notato che IEnumerable<T> deriva da
IEnumerable. Fin qui niente di strano.
La sorpresa è stata scoprire che per implementare IEnumerable<T> devo fornire
l'implementazione di ben due metodi praticamente identici!
Penso che nel 99.9% dei casi uno dei due metodi chiami quell'altro (o
entrambi un metodo d'appoggio). Tra l'altro occorre implementare almeno uno
dei due metodi in modo esplicito.
È la soluzione più semplice. Se guardi i sorgenti della mia collection
(www.codeplex.com/rafcollection) vedrai che ho implementato tutte le
interfacce in modo esplicito e poi le ho girate in un metodo. In questo
modo posso accomodare gli object e varie altre cosette.
Se devi implementare una collection è la strada più conveniente anche
se scrivi un po' più di codice perché le interfacce sono tante e molte
funzionalità (metodi/props) si sovrappongono pur non avendo la stessa
firma.
Post by Kiuhnm
Non mi sembra una soluzione molto pulita.
La situazione con IEnumerator<T> è analoga.
È l'unico modo per garantire la retrocompatibilità rispetto a quando i
generics non c'erano.

Così è, ma l'implementazione esplicita alla fine risolve in modo
sufficientemente elegante.
Post by Kiuhnm
Mi piacerebbe sapere cosa pensate di questa situazione. Vi è capitato in
ambito reale di sentire la mancanza dell'ereditarietà multipla o no?
Si, tante volte, soprattutto nei casi in cui la classe base sia già
impegnata per motivi legacy come nelle winform che devono derivare da
Control.
Se ci fosse probabilmente sarebbe anche abusata e il 'diamond of death'
darebbe altri grattacapi, ma avrei preferito ci fosse.
È stato scelto di non supportare la MI nel framework (è il framework a
non supportarla, per c# non ci sarebbero problemi in teoria) per tenere
più bassa la complessità.
--
Raffaele Rialdi
Microsoft .NET MVP http://mvp.support.microsoft.com -
http://italy.mvps.org UGIdotNET - User Group Italiano .NET
http://www.ugidotnet.org Weblog: http://blogs.ugidotnet.org/raffaele
Kiuhnm
2009-07-22 18:41:50 UTC
Permalink
Post by Raffaele Rialdi [MVP]
Post by Kiuhnm
Studiando i generics in C# ho notato che IEnumerable<T> deriva da
IEnumerable. Fin qui niente di strano.
La sorpresa è stata scoprire che per implementare IEnumerable<T> devo
fornire l'implementazione di ben due metodi praticamente identici!
Penso che nel 99.9% dei casi uno dei due metodi chiami quell'altro (o
entrambi un metodo d'appoggio). Tra l'altro occorre implementare
almeno uno dei due metodi in modo esplicito.
È la soluzione più semplice. Se guardi i sorgenti della mia collection
(www.codeplex.com/rafcollection) vedrai che ho implementato tutte le
interfacce in modo esplicito e poi le ho girate in un metodo. In questo
modo posso accomodare gli object e varie altre cosette.
Se devi implementare una collection è la strada più conveniente anche se
scrivi un po' più di codice perché le interfacce sono tante e molte
funzionalità (metodi/props) si sovrappongono pur non avendo la stessa
firma.
La libreria sembra interessante. Più avanti le do un'occhiata...
Post by Raffaele Rialdi [MVP]
Si, tante volte, soprattutto nei casi in cui la classe base sia già
impegnata per motivi legacy come nelle winform che devono derivare da
Control.
Se ci fosse probabilmente sarebbe anche abusata e il 'diamond of death'
darebbe altri grattacapi, ma avrei preferito ci fosse.
Nel caso
Base
D1 D2
D3
opterei per le seguenti regole:
1) Base è condivisa;
2) D3 DEVE implementare Base, D1 e D2 come se queste fossero semplici
interfacce;
3) D3 può riutilizzare le implementazioni in Base, D1 e D2.

Secondo me questo eviterebbe la scelta di uno schema arbitrario e
costringerebbe il programmatore ad essere cosciente al 100% del problema.

Kiuhnm
Raffaele Rialdi [MVP]
2009-07-23 07:03:19 UTC
Permalink
Post by Kiuhnm
Nel caso
Base
D1 D2
D3
1) Base è condivisa;
2) D3 DEVE implementare Base, D1 e D2 come se queste fossero semplici
interfacce;
3) D3 può riutilizzare le implementazioni in Base, D1 e D2.
Secondo me questo eviterebbe la scelta di uno schema arbitrario e
costringerebbe il programmatore ad essere cosciente al 100% del problema.
Se cerchi in giro per "diamond of death" ne troverai di tutti i colori.
Credo sarebbe comunque sterile entrare nel merito di questa soluzione.
È stato scelto di non supportare la MI nel framework e questo non
lascia alcuna alternativa pratica.
--
Raffaele Rialdi
Microsoft .NET MVP http://mvp.support.microsoft.com -
http://italy.mvps.org UGIdotNET - User Group Italiano .NET
http://www.ugidotnet.org Weblog: http://blogs.ugidotnet.org/raffaele
Kiuhnm
2009-07-23 16:49:04 UTC
Permalink
Post by Raffaele Rialdi [MVP]
Post by Kiuhnm
Nel caso
Base
D1 D2
D3
1) Base è condivisa;
2) D3 DEVE implementare Base, D1 e D2 come se queste fossero semplici
interfacce;
3) D3 può riutilizzare le implementazioni in Base, D1 e D2.
Secondo me questo eviterebbe la scelta di uno schema arbitrario e
costringerebbe il programmatore ad essere cosciente al 100% del problema.
Se cerchi in giro per "diamond of death" ne troverai di tutti i colori.
Questo perché nessuno ha ancora fornito una soluzione soddisfacente o ha
potuto dimostrare che lo è (implementandola in un linguaggio).
Post by Raffaele Rialdi [MVP]
Credo sarebbe comunque sterile entrare nel merito di questa soluzione.
Se con sterile intendi OT, allora concordo.
Come argomento in sé lo trovo invece molto interessante.

Kiuhnm
Raffaele Rialdi [MVP]
2009-07-23 18:29:22 UTC
Permalink
Post by Kiuhnm
Post by Raffaele Rialdi [MVP]
Se cerchi in giro per "diamond of death" ne troverai di tutti i colori.
Questo perché nessuno ha ancora fornito una soluzione soddisfacente o ha
potuto dimostrare che lo è (implementandola in un linguaggio).
Il problema è nato dai problemi di progetti reali incontrati in C++ che
supporta pienamente la multiple inheritance.
Post by Kiuhnm
Post by Raffaele Rialdi [MVP]
Credo sarebbe comunque sterile entrare nel merito di questa soluzione.
Se con sterile intendi OT, allora concordo.
Come argomento in sé lo trovo invece molto interessante.
Intendevo sterile visto che non è disponibile nè noi potremmo
aggiungerla.
A livello teorico è decisamente interessante ma le ipotesi di
implementazione dovrebbero partire dagli inner details del CLR e non
tanto dal linguaggio (e qui andiamo super OT :) )
--
Raffaele Rialdi
Microsoft .NET MVP http://mvp.support.microsoft.com -
http://italy.mvps.org UGIdotNET - User Group Italiano .NET
http://www.ugidotnet.org Weblog: http://blogs.ugidotnet.org/raffaele
Kiuhnm
2009-07-23 19:39:49 UTC
Permalink
Post by Raffaele Rialdi [MVP]
Post by Kiuhnm
Post by Raffaele Rialdi [MVP]
Se cerchi in giro per "diamond of death" ne troverai di tutti i colori.
Questo perché nessuno ha ancora fornito una soluzione soddisfacente o
ha potuto dimostrare che lo è (implementandola in un linguaggio).
Il problema è nato dai problemi di progetti reali incontrati in C++ che
supporta pienamente la multiple inheritance.
Purtroppo la MI nel C++ crea non pochi problemi per via dell'elevata
complessità intrinseca del C++.

Venendo dal C++, volevo più che altro sapere se la comunità del C#
sentisse la mancanza della MI e se questa fosse nella lista delle
feature future. Da quanto dici pare di.

Kiuhnm
Raffaele Rialdi [MVP]
2009-07-23 22:20:54 UTC
Permalink
Post by Kiuhnm
Purtroppo la MI nel C++ crea non pochi problemi per via dell'elevata
complessità intrinseca del C++.
Beh la scrittura di classi e di grafi in C++ è semplice e usare la MI è
altrettanto semplice.
La MI è una caratteristica che impatta soprattutto sull'architettura
del tuo object model. La comunità C++ la usa e ci sono dei casi che si
sono rivelati complessi concettualmente e che non dipendono dal
linguaggio di per se.
Per fare un altro esempio reale, COM ha rinunciato ad esporre la MI,
proprio perché il problema sul lookup su componenti binari implica
grosse problematiche.
Post by Kiuhnm
Venendo dal C++, volevo più che altro sapere se la comunità del C# sentisse
la mancanza della MI e se questa fosse nella lista delle feature future. Da
quanto dici pare di.
di .... ? :)

Io ne sento la necessità e di rado qualcuno "piange" ai miei corsi
quando ne parlo, idem nelle aziende.
--
Raffaele Rialdi
Microsoft .NET MVP http://mvp.support.microsoft.com -
http://italy.mvps.org UGIdotNET - User Group Italiano .NET
http://www.ugidotnet.org Weblog: http://blogs.ugidotnet.org/raffaele
Kiuhnm
2009-07-24 16:11:26 UTC
Permalink
Post by Kiuhnm
Purtroppo la MI nel C++ crea non pochi problemi per via dell'elevata
complessità intrinseca del C++.
La MI è una caratteristica che impatta soprattutto sull'architettura del
tuo object model. La comunità C++ la usa e ci sono dei casi che si sono
rivelati complessi concettualmente e che non dipendono dal linguaggio di
per se.
Penso che la comunità sia come un liquido che prende la forma del
recipiente (il linguaggio, in questo caso specifico). Il problema
principale del C++ deriva dal chaining di costruttori e distruttori. La
complessità intrinseca di cui parlavo è questa. Questo crea problemi a
livello di manutenzione ed estensibilità poiché l'ordine in cui si
eredita è più importante di quanto dovrebbe esserlo. Adesso non trovo
più le relative pubblicazioni, comunque trovi qualche accenno su wikipedia.
La "mia" soluzione a cui ho accennato in un post precedente è
praticamente quella del C++ dove però si forzi l'ereditarietà virtuale
in caso di FJI. Non volevo fare una proposta ufficiale né tantomeno dire
qualcosa di rivoluzionario :-)
Figurati se alla MS non ci hanno pensato... Dicevo solo che io non avrei
rinunciato alla MI e l'avrei copiata quasi pari pari dal C++ (difetti
inclusi). Ma io non penso ai linguaggi come a strumenti per risolvere
problemi reali. Sono più interessato ai linguaggi in quanto tali.
Post by Kiuhnm
Venendo dal C++, volevo più che altro sapere se la comunità del C#
sentisse la mancanza della MI e se questa fosse nella lista delle
feature future. Da quanto dici pare di.
di .... ? :)
No :-)
Io ne sento la necessità e di rado qualcuno "piange" ai miei corsi
quando ne parlo, idem nelle aziende.
Meglio così.

Kiuhnm

Loading...