Qual è lo scopo di questa apparente auto-referenza in C #?

20

Sto valutando un CMS open source chiamato Piranha ( link ) 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 viene definita una classe di BasePage<T> , perché ereditare da Page<T> where T: BasePage<T> ? Quale scopo specifico serve?

    
posta Xami Yen 01.11.2018 - 15:08
fonte

3 risposte

12

Can some help me understand why the class is inheriting from a base of same type?

Non lo è, eredita da Page<T> , ma T è vincolato per essere parametrizzato da un tipo derivato da BasePage<T> .

Per dedurre il motivo, devi considerare come viene effettivamente utilizzato il parametro di tipo T . Dopo un po 'di scavo, mentre ti sposti lungo la catena di ereditarietà, ti troverai su 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 è di assicurarsi che il metodo Create restituisca il tipo meno astratto possibile.

Non so se ne valga la pena, ma forse c'è qualche buon motivo dietro, o potrebbe essere solo per comodità, o forse non c'è troppa sostanza dietro di esso ed è solo un modo troppo elaborato per evitare un cast ( A proposito, non sto insinuando che è il caso qui, sto solo dicendo che le persone lo fanno a volte).

Si noti che questo non consente loro di evitare riflessioni - il api.Pages è un repository di pagine che ottiene typeof(T).Name e lo passa come typeId al metodo contentService.Create (vedi qui ).

    
risposta data 01.11.2018 - 16:35
fonte
5

Un uso comune di questo è legato al concetto di autotipi: un parametro di tipo che si risolve nel tipo corrente. Supponiamo che tu voglia definire un'interfaccia con un metodo clone() . Il metodo clone() deve sempre restituire un'istanza della classe su cui è stata chiamata. Come si dichiara quel metodo? In un sistema generico che ha i self-types, è facile. Dici solo che restituisce self . Quindi, se ho una classe Foo , il metodo clone deve essere dichiarato per restituire Foo . In Java e (da una ricerca frettolosa) C #, questa non è un'opzione. Vedete invece dichiarazioni come quelle che vedete in questa classe. È importante capire che questa non è la stessa cosa di un self-type e le restrizioni che fornisce sono più deboli. Se hai una Foo e una Bar che entrambe derivano da BasePage , puoi (se non mi sbaglio) definire Foo per essere parametrizzato da Bar . Potrebbe essere utile, ma penso che, tipicamente, la maggior parte delle volte, questo verrà usato come un self-type e si capisce che anche se si può scherzare e sostituire con altri tipi, non è qualcosa che si dovrebbe fare. Ho giocato con questa idea molto tempo fa, ma sono giunto alla conclusione che non valeva la pena, perché i limiti dei generici di Java. I generici C # sono ovviamente più completi ma sembra avere questa stessa limitazione.

Un'altra volta che questo approccio viene usato è quando si costruiscono tipi simili a grafici come alberi o altre strutture ricorsive. La dichiarazione consente ai tipi di soddisfare i requisiti di Page ma ulteriormente perfezionare il tipo. Potresti vederlo in una struttura ad albero. Ad esempio un Node potrebbe essere parametrizzato da Node per 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 cosa sta succedendo qui.

    
risposta data 01.11.2018 - 17:51
fonte
3

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

Come accenna, c'è ancora un sacco di riflessi in corso, e alla fine solo il nome del tipo viene utilizzato per risolvere il tipo di pagina. Il motivo è che puoi caricare anche modelli dinamici, cioè materializzare il modello senza avere accesso al tipo CLR che lo ha creato in primo luogo.

    
risposta data 26.12.2018 - 13:32
fonte

Leggi altre domande sui tag