Qual é o objetivo dessa aparente auto-referência em C #?

21

Estou avaliando um CMS de código aberto chamado Piranha ( http://piranhacms.org/ ) para uso em um dos meus projetos. Achei o código a seguir interessante e um pouco confuso, pelo menos para mim. Alguns podem me ajudar a entender por que a classe está herdando de uma base do mesmo 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 uma classe de BasePage<T>está sendo definida, por que herdar Page<T> where T: BasePage<T>? Que finalidade específica ela serve?

Xami Yen
fonte
7
Apesar dos votos negativos e votos próximos, acho que a comunidade entendeu errado. Esta é uma pergunta claramente definida, relacionada a uma decisão de design específica e não trivial, a essência do assunto deste site.
Robert Harvey
Apenas fique por perto e reabra-o quando estiver fechado.
David Arno
Leia sobre o conceito de polimorfismo delimitado por F :)
Eyvind
1
@ Heyvind eu realmente fiz. Para os interessados em ler sobre polimorfismo F-limitada, aqui está o link staff.ustc.edu.cn/~xyfeng/teaching/FOPL/lectureNotes/...
Xami Yen

Respostas:

13

Alguns podem me ajudar a entender por que a classe está herdando de uma base do mesmo tipo?

Não é, é herdado de Page<T>, mas Té restrito a ser parametrizado por um tipo derivado BasePage<T>.

Para inferir o motivo, você deve observar como o parâmetro type Té realmente usado. Após algumas escavações, à medida que você sobe na cadeia de herança, você se depara com esta 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);
    }
}

Tanto quanto posso ver, o único objetivo da restrição genérica é garantir que o Createmétodo retorne o tipo menos abstrato possível.

Mas não tenho certeza se vale a pena, mas talvez haja alguma boa razão por trás disso, ou possa ser apenas por conveniência, ou talvez não haja muita substância por trás disso e seja apenas uma maneira muito elaborada de evitar um elenco (BTW, eu não estou sugerindo que é o caso aqui, estou apenas dizendo que as pessoas fazem isso às vezes).

Observe que isso não permite que eles evitem a reflexão - api.Pagesé um repositório de páginas que é obtido typeof(T).Namee o passa typeIdpara o contentService.Createmétodo ( veja aqui ).

Filip Milovanović
fonte
5

Um uso comum disso está relacionado ao conceito de auto-tipos: um parâmetro de tipo que resolve para o tipo atual. Digamos que você queira definir uma interface com um clone()método. O clone()método sempre deve retornar uma instância da classe na qual foi chamado. Como você declara esse método? Em um sistema genérico que possui tipos próprios, é fácil. Você acabou de dizer que retorna self. Portanto, se eu tiver uma classe Foo, o método clone deve ser declarado para retornar Foo. Em Java e (a partir de uma pesquisa superficial) em C #, essa não é uma opção. Você vê declarações como a que você vê nesta classe. É importante entender que isso não é o mesmo que um tipo próprio e as restrições que ele fornece são mais fracas. Se você tem Foouma Barclasse e uma que derivam deBasePage, você pode (se não me engano) definir Foo a ser parametrizado por Bar. Isso pode ser útil, mas acho que normalmente, na maioria das vezes, isso será usado como um tipo pessoal e apenas entendemos que, embora você possa brincar e substituir por outros tipos, não é algo que você deve fazer. Eu brinquei com essa idéia há muito tempo, mas cheguei à conclusão de que não valia a pena o esforço devido às limitações dos genéricos Java. É claro que os genéricos de C # são mais completos, mas parece ter essa mesma limitação.

Outra vez que essa abordagem é usada é quando você está construindo tipos semelhantes a gráficos, como árvores ou outras estruturas recursivas. A declaração permite que os tipos atendam aos requisitos de Página, mas refina ainda mais o tipo. Você pode ver isso em uma estrutura de árvore. Por exemplo, um Nodepode ser parametrizado Nodepara permitir que as implementações definam que elas não são apenas Árvores contendo qualquer tipo de Nó, mas um sub-tipo específico de Nó (geralmente seu próprio tipo). Acho que é mais isso que está acontecendo aqui.

JimmyJames
fonte
3

Sendo a pessoa que realmente escreveu o código, posso confirmar que Filip está correto e o genérico de referência própria é de fato uma conveniência para fornecer um método Create digitado na classe base.

Como ele menciona, ainda há muita reflexão acontecendo e, no final, apenas o nome do tipo é usado para resolver o tipo de página. A razão para isso é que você também pode carregar modelos dinâmicos, ou seja, materializar o modelo sem ter acesso ao tipo de CLR que o criou em primeiro lugar.

Håkan Edling
fonte