¿Cuál es el propósito de esta autorreferencia aparente en C #?

21

Estoy evaluando un CMS de código abierto llamado Piranha ( http://piranhacms.org/ ) para usar en uno de mis proyectos. El siguiente código me pareció interesante y un poco confuso, al menos para mí. ¿Pueden algunos ayudarme a entender por qué la clase hereda de una base del mismo 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; }
}

Si BasePage<T>se está definiendo una clase de , ¿por qué heredar de Page<T> where T: BasePage<T>? ¿Para qué propósito específico sirve?

Xami Yen
fuente
77
A pesar de los votos negativos y los votos cerrados, creo que la comunidad se equivocó en este caso. Esta es una pregunta clara que tiene que ver con una decisión de diseño específica y no trivial, la esencia de lo que trata este sitio.
Robert Harvey
Solo espera y vuelve a abrirlo cuando se cierre.
David Arno
Lea sobre el concepto de polimorfismo limitado por F :)
Eyvind
1
@Eyvind en realidad lo hice. Para aquellos interesados ​​en leer sobre el polimorfismo limitado por F, aquí está el enlace staff.ustc.edu.cn/~xyfeng/teaching/FOPL/lectureNotes/…
Xami Yen

Respuestas:

13

¿Pueden algunos ayudarme a entender por qué la clase hereda de una base del mismo tipo?

No lo es, está heredando de Page<T>, pero Testá limitado a ser parametrizado por un tipo derivado de BasePage<T>.

Para inferir por qué, debe observar cómo Tse usa realmente el parámetro de tipo . Después de investigar un poco, a medida que avanzas en la cadena de herencia, llegarás a esta clase:

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

Hasta donde puedo ver, el único propósito de la restricción genérica es asegurarse de que el Createmétodo devuelva el menor tipo abstracto posible.

Sin embargo, no estoy seguro de si vale la pena, pero tal vez haya una buena razón detrás de esto, o podría ser solo por conveniencia, o tal vez no haya demasiada sustancia detrás y sea solo una forma demasiado elaborada de evitar un yeso (BTW, I No estoy insinuando que ese sea el caso aquí, solo digo que la gente hace eso a veces).

Tenga en cuenta que esto no les permite evitar la reflexión: api.Pageses un repositorio de páginas que obtiene typeof(T).Namey lo pasa typeIdal contentService.Createmétodo ( ver aquí ).

Filip Milovanović
fuente
5

Un uso común de esto está relacionado con el concepto de auto-tipos: un parámetro de tipo que resuelve el tipo actual. Digamos que desea definir una interfaz con un clone()método. El clone()método siempre debe devolver una instancia de la clase en la que se llamó. ¿Cómo declaras ese método? En un sistema genérico que tiene auto-tipos, es fácil. Solo dices que vuelve self. Entonces, si tengo una clase Foo, el método de clonación debe declararse como devuelto Foo. En Java y (desde una búsqueda superficial) C #, esta no es una opción. En su lugar, ve declaraciones como las que ve en esta clase. Es importante entender que esto no es lo mismo que un auto-tipo y las restricciones que proporciona son más débiles. Si tiene una Fooy una Barclase que derivan deBasePage, puede (si no me equivoco) definir Foo para ser parametrizado por Bar. Eso puede ser útil, pero creo que, por lo general, la mayoría de las veces, esto se usará como un tipo automático y se entiende que, aunque puede burlarse y sustituir con otros tipos, no es algo que deba hacer. Jugué con esta idea hace mucho tiempo, pero llegué a la conclusión de que no valía la pena el esfuerzo debido a las limitaciones de los genéricos de Java. Los genéricos de C # son, por supuesto, más completos, pero parece tener esta misma limitación.

Otra vez que se utiliza este enfoque es cuando está construyendo tipos de gráficos como árboles u otras estructuras recursivas. La declaración permite que los tipos cumplan con los requisitos de la página, pero refinan aún más el tipo. Puede ver esto en una estructura de árbol. Por ejemplo, se Nodepodría parametrizar Nodepara permitir que las implementaciones definan que no son solo Árboles que contienen cualquier tipo de Nodo sino un subtipo específico de Nodo (generalmente su propio tipo). Creo que eso es más de lo que está sucediendo aquí.

JimmyJames
fuente
3

Siendo la persona que realmente escribió el código, puedo confirmar que Filip es correcto y que el genérico autorreferenciado es, de hecho, una conveniencia para proporcionar un método de creación escrito en la clase base.

Como él menciona, todavía hay mucha reflexión, y al final solo se usa el nombre del tipo para resolver el tipo de página. La razón de esto es que también puede cargar modelos dinámicos, es decir, materializar el modelo sin tener acceso al tipo CLR que lo creó en primer lugar.

Håkan Edling
fuente