Qual è lo scopo di questo apparente auto-riferimento in C #?


21

Sto valutando un CMS open source chiamato Piranha ( http://piranhacms.org/ ) da utilizzare in uno dei miei progetti. Ho trovato il seguente codice interessante e un po 'confuso, almeno per me. Qualcuno può aiutarmi a capire perché la classe eredita da una base dello stesso tipo?

public abstract class BasePage<T> : Page<T> where T : BasePage<T>
{
    /// <summary>
    /// Gets/sets the page heading.
    /// </summary>
    [Region(SortOrder = 0)]
    public Regions.PageHeading Heading { get; set; }
}

Se BasePage<T>viene definita una classe di , perché ereditare da Page<T> where T: BasePage<T>? Quale scopo specifico serve?



7
Nonostante i voti negativi e gli stretti voti, penso che la comunità abbia sbagliato su questo. Questa è una domanda chiaramente definita che riguarda una decisione di progettazione specifica e non banale, l'essenza stessa di ciò che tratta questo sito.
Robert Harvey,

Basta restare in giro e riaprirlo quando viene chiuso.
David Arno,

Leggi il concetto di polimorfismo legato all'F :)
Eyvind,

1
@Eyvind l'ho fatto davvero. Per coloro che sono interessati a leggere sul polimorfismo legato all'F
Xami Yen,

Risposte:


13

Qualcuno può aiutarmi a capire perché la classe eredita da una base dello stesso tipo?

Non lo è, sta ereditando Page<T>, ma Tè costretto a essere parametrizzato da un tipo da cui deriva BasePage<T>.

Per dedurre il perché, è necessario esaminare come Tviene effettivamente utilizzato il parametro type . Dopo aver scavato un po ', mentre salite la catena dell'eredità, vi imbatterete in questa classe:

( github )

public class GenericPage<T> : PageBase where T : GenericPage<T>
{
    public bool IsStartPage {
        get { return !ParentId.HasValue && SortOrder == 0; }
    }

    public GenericPage() : base() { }

    public static T Create(IApi api, string typeId = null)
    {
        return api.Pages.Create<T>(typeId);
    }
}

Per quanto posso vedere, l'unico scopo del vincolo generico è assicurarsi che il Createmetodo restituisca il tipo meno astratto possibile.

Non sono sicuro se ne valga la pena, però, ma forse c'è qualche buona ragione dietro, o potrebbe essere solo per comodità, o forse non c'è troppa sostanza dietro di esso ed è solo un modo eccessivamente elaborato per evitare un cast (BTW, I non sto insinuando che è il caso qui, sto solo dicendo che la gente lo fa a volte).

Nota che ciò non consente loro di evitare la riflessione: api.Pagesè un repository di pagine che ottiene typeof(T).Namee lo passa typeIdal contentService.Createmetodo ( vedi qui ).


5

Un uso comune di questo è legato al concetto di auto-tipi: un parametro di tipo che si risolve con il tipo corrente. Diciamo che vuoi definire un'interfaccia con un clone()metodo. Il clone()metodo deve sempre restituire un'istanza della classe su cui è stato chiamato. Come si dichiara questo metodo? In un sistema generico che ha i tipi di sé, è facile. Dici solo che ritorna self. Quindi, se ho una classe Foo, il metodo clone deve essere dichiarato per tornare Foo. In Java e (da una ricerca rapida) C #, questa non è un'opzione. Invece vedi dichiarazioni come quelle che vedi in questa classe. È importante capire che questa non è la stessa cosa di un tipo di sé e che le restrizioni fornite sono più deboli. Se hai una Fooe una Barclasse da cui entrambe derivanoBasePage, puoi (se non sbaglio) definire Foo da parametrizzare Bar. Ciò potrebbe essere utile, ma in genere penso che, nella maggior parte dei casi, questo verrà utilizzato come un tipo di auto-tipo ed è appena compreso che, sebbene tu possa andare in giro e sostituire con altri tipi, non è qualcosa che dovresti fare. Ho giocato con questa idea molto tempo fa, ma sono giunto alla conclusione che non valeva la pena a causa dei limiti dei generici Java. I generici C # sono ovviamente più completi ma sembra avere questa stessa limitazione.

Un'altra volta che questo approccio viene utilizzato è quando si creano tipi simili a grafici come alberi o altre strutture ricorsive. La dichiarazione consente ai tipi di soddisfare i requisiti di Page ma di perfezionare ulteriormente il tipo. Potresti vederlo in una struttura ad albero. Ad esempio, a Nodepotrebbe essere parametrizzato Nodeper consentire alle implementazioni di definire che non sono solo alberi contenenti qualsiasi tipo di nodo ma uno specifico sottotipo di nodo (di solito il loro tipo). Penso che sia più di quello che sta succedendo qui.


3

Essendo la persona che ha effettivamente scritto il codice, posso confermare che Filip è corretto e che il generico autoreferenziale è in realtà una comodità per fornire un metodo Create tipizzato sulla classe base.

Come menziona, c'è ancora molta riflessione in corso, e alla fine solo il nome del tipo viene utilizzato per risolvere il tipo di pagina. La ragione di ciò è che è possibile caricare anche modelli dinamici, ovvero materializzare il modello senza avere accesso al tipo CLR che lo ha creato in primo luogo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.