Was ist der Zweck dieser offensichtlichen Selbstreferenz in C #?

21

Ich evaluiere ein Open-Source-CMS namens Piranha ( http://piranhacms.org/ ) zur Verwendung in einem meiner Projekte. Ich fand den folgenden Code interessant und zumindest für mich etwas verwirrend. Können mir einige helfen, zu verstehen, warum die Klasse von einer Basis desselben Typs erbt?

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

Wenn eine Klasse von BasePage<T>definiert wird, warum von erben Page<T> where T: BasePage<T>? Welchem ​​spezifischen Zweck dient es?

Xami Yen
quelle
7
Trotz der Abstimmungen und engen Abstimmungen, denke ich, hat die Community diesbezüglich etwas falsch gemacht. Dies ist eine klar umrissene Frage, die mit einer spezifischen und nicht trivialen Designentscheidung zu tun hat.
Robert Harvey
Einfach rumhängen und wieder öffnen, wenn es geschlossen wird.
David Arno
Lesen Sie mehr über das Konzept des F-gebundenen Polymorphismus :)
Eyvind
1
@ Eyvind habe ich eigentlich gemacht. Für diejenigen, die sich für F-Bounded Polymorphismus interessieren, ist hier der Link staff.ustc.edu.cn/~xyfeng/teaching/FOPL/lectureNotes/…
Xami Yen

Antworten:

13

Können mir einige helfen, zu verstehen, warum die Klasse von einer Basis desselben Typs erbt?

Es ist nicht, es erbt von Page<T>, aber es Tist darauf beschränkt, durch einen Typ parametrisiert zu werden, von dem es abgeleitet ist BasePage<T>.

Um zu schließen, warum, müssen Sie sich ansehen, wie der Typparameter Ttatsächlich verwendet wird. Nach einigem Graben werden Sie auf diese Klasse stoßen, wenn Sie die Vererbungskette hinaufsteigen:

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

Soweit ich sehen kann, besteht der einzige Zweck der generischen Einschränkung darin, sicherzustellen, dass die CreateMethode den am wenigsten abstrakten Typ zurückgibt, der möglich ist.

Ich bin mir nicht sicher, ob es sich lohnt, aber vielleicht steckt ein guter Grund dahinter, oder es könnte nur der Einfachheit halber sein, oder vielleicht steckt nicht allzu viel Substanz dahinter und es ist nur ein überaus ausgeklügelter Weg, eine Besetzung zu vermeiden (Übrigens, ich Ich will damit nicht andeuten, dass das hier der Fall ist. Ich sage nur, dass die Leute das manchmal tun.

Beachten Sie, dass dies ihnen nicht erlaubt, Reflexionen zu vermeiden. Dies api.Pagesist ein Repository von Seiten, die abgerufen typeof(T).Nameund als typeIdan die contentService.CreateMethode übergeben werden ( siehe hier ).

Filip Milovanović
quelle
5

Eine gebräuchliche Verwendung davon hängt mit dem Konzept der Selbsttypen zusammen: ein Typparameter, der in den aktuellen Typ aufgelöst wird. Angenommen, Sie möchten eine Schnittstelle mit einer clone()Methode definieren. Die clone()Methode muss immer eine Instanz der Klasse zurückgeben, für die sie aufgerufen wurde. Wie deklarieren Sie diese Methode? In einem generischen System mit Selbsttypen ist dies einfach. Sie sagen nur, es kehrt zurück self. Wenn ich also eine Klasse habe Foo, muss die Klonmethode deklariert werden, um zurückzukehren Foo. In Java und (von einer flüchtigen Suche) C # ist dies keine Option. Stattdessen sehen Sie Deklarationen, die denen in dieser Klasse entsprechen. Es ist wichtig zu verstehen, dass dies nicht dasselbe ist wie ein Selbsttyp, und die darin enthaltenen Einschränkungen sind schwächer. Wenn Sie eine Foound eine BarKlasse haben, von denen beide abgeleitet sindBasePage, Sie können (wenn ich mich nicht irre) definieren, dass Foo durch parametrisiert wird Bar. Das mag nützlich sein, aber ich denke, normalerweise wird dies die meiste Zeit wie ein Selbsttyp verwendet, und es versteht sich von selbst, dass Sie es nicht tun sollten, obwohl Sie herumspielen und durch andere Typen ersetzen können. Ich habe vor langer Zeit mit dieser Idee herumgespielt, bin aber zu dem Schluss gekommen, dass sich die Mühe aufgrund der Einschränkungen von Java-Generika nicht gelohnt hat. C # -Generika sind natürlich umfassender ausgestattet, es scheint jedoch die gleiche Einschränkung zu geben.

Ein anderes Mal wird dieser Ansatz verwendet, wenn Sie grafische Typen wie Bäume oder andere rekursive Strukturen erstellen. Die Deklaration ermöglicht es Typen, die Anforderungen von Page zu erfüllen, aber den Typ weiter zu verfeinern. Möglicherweise sehen Sie dies in einer Baumstruktur. Zum Beispiel Nodekönnte a parametrisiert werden Node, damit Implementierungen definieren können, dass es sich nicht nur um Bäume handelt, die einen Knotentyp enthalten, sondern um einen bestimmten Knotentyp (normalerweise einen eigenen Typ). Ich denke, das ist mehr, was hier vor sich geht.

JimmyJames
quelle
3

Als die Person, die den Code tatsächlich geschrieben hat, kann ich bestätigen, dass Filip korrekt ist und das selbstreferenzierende Generikum in der Tat eine Annehmlichkeit für die Bereitstellung einer typisierten Create-Methode für die Basisklasse darstellt.

Wie er erwähnt, wird immer noch viel nachgedacht, und am Ende wird nur der Name des Typs zum Auflösen des Seitentyps verwendet. Der Grund dafür ist, dass Sie auch dynamische Modelle laden können, dh das Modell materialisieren können, ohne Zugriff auf den CLR-Typ zu haben, der es ursprünglich erstellt hat.

Håkan Edling
quelle