Estoy making a jquery clone para C#. Ahora mismo lo tengo configurado para que cada método sea un método de extensión en IEnumerable<HtmlNode>
, por lo que funciona bien con proyectos existentes que ya están usando HtmlAgilityPack
. Pensé que podía escapar sin conservar el estado ... sin embargo, noté que jQuery tiene dos métodos .andSelf
y .end
que "reventan" los elementos coincidentes más recientes de una pila interna. Puedo imitar esta funcionalidad si cambio mi clase para que siempre opere en objetos SharpQuery en lugar de enumerables, pero todavía hay un problema.¿Cómo diseñar mi API C# jQuery de modo que no sea confuso de usar?
Con JavaScript, se le da el documento HTML automáticamente, pero cuando se trabaja en C# tiene que cargarlo explícitamente, y puede usar más de un documento si lo desea. Parece que cuando llamas al $('xxx')
esencialmente estás creando un nuevo objeto jQuery y comenzando de nuevo con una pila vacía. En C#, no querrá hacer eso, porque no quiere volver a cargar/volver a buscar el documento desde la web. Entonces, en su lugar, la carga una vez en un objeto SharpQuery o en una lista de HtmlNodes (solo necesita el DocumentNode para comenzar).
En la documentación de jQuery, que dan este ejemplo
$('ul.first').find('.foo')
.css('background-color', 'red')
.end().find('.bar')
.css('background-color', 'green')
.end();
no tengo un método de inicialización porque no puedo sobrecargar el operador ()
, por lo que acaba de empezar con sq.Find()
lugar, que opera en el raíz del documento, esencialmente haciendo lo mismo. Pero la gente intentará escribir sq.Find()
en una línea, y luego sq.Find()
en algún momento, y (con razón) esperan que vuelva a funcionar en la raíz del documento ... pero si mantengo el estado, entonces usted Acabo de modificar el contexto después de la primera llamada.
Entonces ... ¿cómo debo diseñar mi API? ¿Debo agregar otro método Init
con el que todas las consultas comiencen con que restablece la pila (pero, entonces, ¿cómo los obligo a comenzar con eso?), O agreguen un Reset()
al que tienen que llamar al final de su línea? ¿Sobrecargo el []
y les digo que empiecen con eso? ¿Digo "olvídalo, de todos modos, nadie usa esas funciones conservadas por el estado"?
Básicamente, ¿cómo le gustaría que el ejemplo jQuery se escriba en C#?
sq["ul.first"].Find(".foo") ...
Desventajas: los abusos de la propiedad[]
.sq.Init("ul.first").Find(".foo") ...
Desventajas: En realidad nada obliga al programador para comenzar con Init, a menos que añado algún mecanismo raro "inicializado"; el usuario puede intentar comenzar con.Find
y no obtener el resultado que esperaba. Además,Init
yFind
son prácticamente idénticos de todos modos, excepto que el primero restablece la pila también.sq.Find("ul.first").Find(".foo") ... .ClearStack()
Desventajas: programador puede olvidarse de limpiar la pila.No se puede hacer.
end()
no implementado.Utilice dos objetos diferentes.
Quizás useHtmlDocument
como la base con la que todas las consultas deben comenzar, y luego cada método devuelve un objetoSharpQuery
que puede encadenarse. De esta manera, elHtmlDocument
siempre mantiene el estado inicial, pero los objetosSharpQuery
pueden tener estados diferentes. Desafortunadamente, esto significa que tengo que implementar un montón de cosas dos veces (una para HtmlDocument, una para el objeto SharpQuery).new SharpQuery(sq).Find("ul.first").Find(".foo") ...
Las copias del constructor una referencia al documento, sino que restablece la pila.
IMO 5) es la mejor opción. Es ciertamente legible e intuitivo que las consultas comiencen desde 'HtmlDocument', y en cuanto al trabajo duplicado, con suerte puede implementarlo de modo que solo las firmas de métodos estén duplicadas, pero no la lógica real. (Encadenando las llamadas a una clase común que hace el trabajo.) También me gusta la opción 1) y personalmente no la veo como un "abuso" de los corchetes. :) –
@Kirk: '[]' generalmente indica una operación 'O (1)' ... como si los datos ya estuvieran indexados. En este caso, en realidad tiene que hacer una búsqueda completa. – mpen
Sin embargo, estoy inclinado hacia (1) ahora. En realidad, comencé a codificarlo de esta manera. Esto evita que los usuarios salten directamente con '.Find()' porque el contexto no está configurado hasta que llamen a '[]'. es decir, '.Find()' simplemente no encontrará nada porque no tiene nada que buscar. '[]' restablece el contexto al nodo del documento y luego pueden comenzar la búsqueda. – mpen