Какова цель этой очевидной ссылки на себя в C #?

21

Я оцениваю CMS с открытым исходным кодом под названием Piranha ( http://piranhacms.org/ ) для использования в одном из моих проектов. Я нашел следующий код интересным и немного запутанным, по крайней мере, для меня. Может ли кто-нибудь помочь мне понять, почему класс наследуется от базы того же типа?

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; }
}

Если класс BasePage<T>определяется, зачем наследовать от Page<T> where T: BasePage<T>? Какой конкретной цели это служит?

Ксами Йен
источник
7
Несмотря на отрицательные и близкие голоса, я думаю, что сообщество неправильно поняло это. Это четко сформулированный вопрос, связанный с конкретным и нетривиальным дизайнерским решением, самой сущностью этого сайта.
Роберт Харви
Просто торчите и снова откройте его, когда он закроется.
Дэвид Арно
Читайте о концепции F-ограниченного полиморфизма :)
Эйвинд,
1
@ Эйвинд, я действительно сделал. Для тех, кто интересуется чтением о F-ограниченном полиморфизме, вот ссылка staff.ustc.edu.cn/~xyfeng/teaching/FOPL/lectureNotes/…
Xami Yen

Ответы:

13

Может ли кто-нибудь помочь мне понять, почему класс наследуется от базы того же типа?

Он не наследуется Page<T>, но Tсам по себе ограничен параметризацией по типу, производному от BasePage<T>.

Чтобы понять почему, вам нужно посмотреть, как на Tсамом деле используется параметр типа . После некоторого рытья по мере продвижения по цепочке наследования вы натолкнетесь на этот класс:

( 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);
    }
}

Насколько я вижу, единственная цель общего ограничения - убедиться, что Createметод возвращает наименьший возможный абстрактный тип.

Не уверен, что это того стоит, но, возможно, за этим есть какая-то веская причина, или это может быть только для удобства, или, может быть, за этим не стоит слишком много вещества, и это просто слишком сложный способ избежать броска (кстати, я Я не имею в виду, что это так, я просто говорю, что люди делают это иногда).

Обратите внимание , что это не позволяет им избежать отражения - api.Pagesявляется хранилищем страниц, получающими typeof(T).Name, и передает его как typeIdк contentService.Createметоду ( смотрите здесь ).

Филипп Милованович
источник
5

Одно из распространенных применений этого связано с концепцией самотипов: параметр типа, который разрешает текущий тип. Допустим, вы хотите определить интерфейс с clone()методом. clone()Метод всегда должен возвращать экземпляр класса , на котором он был вызван. Как вы объявляете этот метод? В системе дженериков, которая имеет свои типы, это легко. Вы просто говорите, что это возвращается self. Так что, если у меня есть класс Foo, метод clone должен быть объявлен для возврата Foo. В Java и (из простого поиска) C # это не вариант. Вместо этого вы видите объявления, подобные тому, что вы видите в этом классе. Важно понимать, что это не то же самое, что и тип личности, и ограничения, которые он предоставляет, более слабые. Если у вас есть Fooи Barкласс, который оба являются производными отBasePage, вы можете (если я не ошибаюсь) определить Foo для параметризации Bar. Это может быть полезно, но я думаю, что, как правило, в большинстве случаев это будет использоваться как самопечатание, и просто понятно, что даже если вы можете обмануть и заменить другими типами, это не то, что вы должны делать. Я давно обдумал эту идею, но пришел к выводу, что это не стоит затраченных усилий из-за ограничений дженериков Java. Обобщения C #, конечно, более полнофункциональны, но, похоже, имеют то же ограничение.

В другой раз этот подход используется, когда вы создаете графоподобные типы, такие как деревья или другие рекурсивные структуры. Объявление позволяет типам соответствовать требованиям Page, но дополнительно уточняет тип. Вы можете увидеть это в древовидной структуре. Например Node, параметр может быть параметризован с помощью, Nodeчтобы реализации могли определить, что это не просто деревья, содержащие какой-либо тип узла, а определенный подтип узла (обычно их собственный тип). Я думаю, что это больше того, что здесь происходит.

JimmyJames
источник
3

Будучи человеком, который на самом деле написал код, я могу подтвердить, что Filip верен, а самоссылочный дженерик на самом деле удобен для предоставления типизированного метода Create в базовом классе.

Как он упоминает, еще много размышлений происходит, и в конце концов только имя типа используется для разрешения типа страницы. Причина этого заключается в том, что вы также можете загружать динамические модели, то есть материализовать модель, не имея доступа к типу CLR, который ее создал.

Хокан Эдлинг
источник