2010-11-04 25 views
5

Je suis making a jquery clone pour C#. En ce moment, je l'ai mis en place de sorte que chaque méthode est une méthode d'extension sur IEnumerable<HtmlNode> de sorte qu'il fonctionne bien avec les projets existants qui utilisent déjà HtmlAgilityPack. Je pensais pouvoir m'enfuir sans préserver l'état ... cependant, j'ai remarqué que jQuery a deux méthodes .andSelf et .end qui "pop" les éléments les plus récemment appariés d'une pile interne. Je peux imiter cette fonctionnalité si je change ma classe afin qu'elle fonctionne toujours sur les objets SharpQuery au lieu des énumérables, mais il y a toujours un problème.Comment concevoir mon API C# jQuery de telle sorte qu'elle ne prête pas à confusion?

Avec JavaScript, le document Html vous est automatiquement fourni, mais lorsque vous travaillez en C#, vous devez le charger explicitement, et vous pouvez utiliser plus d'un document si vous le souhaitez. Il semble que lorsque vous appelez $('xxx'), vous créez essentiellement un nouvel objet jQuery et commencez avec une pile vide. En C#, vous ne voudriez pas faire cela, parce que vous ne voulez pas recharger/recharger le document sur le web. Donc, à la place, vous le chargez une fois dans un objet SharpQuery ou dans une liste de HtmlNodes (vous avez juste besoin du DocumentNode pour commencer).

Dans les docs jQuery, ils donnent cet exemple

$('ul.first').find('.foo') 
    .css('background-color', 'red') 
.end().find('.bar') 
    .css('background-color', 'green') 
.end(); 

Je n'ai pas une méthode d'initialisation parce que je ne peux pas surcharger l'opérateur (), de sorte que vous venez de commencer avec sq.Find() à la place, qui fonctionne sur la racine du document, essentiellement faire la même chose. Mais alors les gens vont essayer d'écrire sq.Find() sur une ligne, puis sq.Find() quelque part sur la route, et (légitimement) s'attendre à ce qu'il fonctionne à nouveau sur la racine du document ... mais si je suis en état, alors vous J'ai juste modifié le contexte après le premier appel.

Alors ... comment devrais-je concevoir mon API? Dois-je ajouter une autre méthode Init que toutes les requêtes devraient commencer avec qui réinitialise la pile (mais alors comment puis-je les forcer à commencer par cela?), Ou ajouter un Reset() qu'ils doivent appeler à la fin de leur ligne? Dois-je surcharger le [] à la place et leur dire de commencer avec ça? Est-ce que je dis "oubliez-le, personne n'utilise ces fonctions préservées par l'état de toute façon?"

Fondamentalement, comment voulez-vous que cet exemple jQuery soit écrit en C#?

  1. sq["ul.first"].Find(".foo") ...
    écroulements: la propriété [] Exactions.

  2. sq.Init("ul.first").Find(".foo") ...
    écroulements: Rien oblige vraiment le programmeur de commencer par Init, à moins que j'ajouter un mécanisme bizarre « initialisé »; l'utilisateur peut essayer de commencer avec .Find et ne pas obtenir le résultat qu'il attendait. En outre, Init et Find sont à peu près identiques de toute façon, sauf que le premier réinitialise la pile aussi.

  3. sq.Find("ul.first").Find(".foo") ... .ClearStack()
    écroulements: programmeur peut oublier de dégager la pile.

  4. Impossible de le faire.
    end() pas implémenté.

  5. Utilisez deux objets différents.
    Peut-être utiliser HtmlDocument comme base que toutes les requêtes devraient commencer, puis chaque méthode retourne ensuite un objet SharpQuery qui peut être chaîné. De cette façon, le HtmlDocument conserve toujours l'état initial, mais les objets SharpQuery peuvent avoir des états différents. Cela signifie malheureusement que je dois implémenter un tas de choses deux fois (une fois pour HtmlDocument, une fois pour l'objet SharpQuery).

  6. new SharpQuery(sq).Find("ul.first").Find(".foo") ...
    Les copies du constructeur une référence au document, mais remet à zéro la pile.

+2

IMO 5) est la meilleure option. Il est certainement lisible et intuitif pour les requêtes de commencer à partir de 'HtmlDocument', et quant au travail en double - espérons que vous pouvez l'implémenter afin que seules les signatures de méthodes soient dupliquées, mais pas la logique réelle. (Enchaînant les appels à une classe commune qui fait le travail.) J'aime aussi l'option 1) et personnellement ne la vois pas comme un «abus» des parenthèses. :) –

+0

@Kirk: '[]' indique généralement une opération 'O (1)' ... comme si les données étaient déjà indexées. Dans ce cas, il doit faire une recherche complète. – mpen

+0

Néanmoins, je penche vers (1) maintenant. En fait, j'ai commencé à le coder de cette façon. Cela empêche les utilisateurs de sauter directement avec '.Find()' car le contexte n'est pas défini avant d'appeler '[]'. c'est-à-dire que '.Find()' ne trouve rien parce qu'il n'a rien à chercher. '[]' réinitialise le contexte sur le noeud de document, puis il peut commencer à chercher. – mpen

Répondre

4

Je pense que la principale pierre d'achoppement que vous rencontrez ici est que vous essayez de vous en tirer simplement en ayant un objet SharpQuery pour chaque document. Ce n'est pas comme ça que fonctionne jQuery; en général, les objets jQuery sont immuables. Lorsque vous appelez une méthode qui change l'ensemble des éléments (comme find ou end ou add), il ne modifie pas l'objet existant, mais retourne une nouvelle:

var theBody = $('body'); 
// $('body')[0] is the <body> 
theBody.find('div').text('This is a div'); 
// $('body')[0] is still the <body> 

(voir le documentation of end pour plus d'infos)

SharpQuery doit fonctionner de la même manière. Une fois que vous créez un objet SharpQuery avec un document, les appels de méthodes doivent renvoyer les nouveaux objets SharpQuery, référençant un ensemble différent d'éléments du même document. Par exemple:

var sq = SharpQuery.Load(new Uri("http://api.jquery.com/category/selectors/")); 
var header = sq.Find("h1"); // doesn't change sq 
var allTheLinks = sq.Find(".title-link") // all .title-link in the whole document; also doesn't change sq 
var someOfTheLinks = header.Find(".title-link"); // just the .title-link in the <h1>; again, doesn't change sq or header 

Les avantages de cette approche sont multiples. Parce que sq, header, allTheLinks, etc. sont tous de la même classe, vous n'avez qu'une implémentation de chaque méthode. Cependant, chacun de ces objets référence le même document, donc vous n'avez pas plusieurs copies de chaque nœud, et les modifications apportées aux nœuds sont reflétées dans chaque objet SharpQuery sur ce document (par exemple après allTheLinks.text("foo"), someOfTheLinks.text() == "foo".).

La mise en œuvre end et les autres manipulations à base de pile deviennent également faciles. Lorsque chaque méthode crée un nouvel objet SharpQuery filtré à partir d'un autre, il conserve une référence à cet objet parent (allTheLinks à header, header à sq). Alors end est aussi simple que le retour d'une nouvelle SharpQuery contenant les mêmes éléments que le parent, comme:

public SharpQuery end() 
{ 
    return new SharpQuery(this.parent.GetAllElements()); 
} 

(ou cependant votre syntaxe secoue.)

Je pense que cette approche vous obtiendrez le plus jQuery -comme un comportement, avec une mise en œuvre assez facile.Je vais certainement garder un œil sur ce projet; c'est une bonne idée.

+0

Ahh..clever! Je pensais retourner un nouvel objet, mais je ne pouvais pas comprendre comment je pourrais maintenir la pile. Garder une référence au parent ... si simple>. < – mpen

+0

Grande mise à jour: http://sharp-query.googlecode.com/svn/trunk/HtmlAgilityPlus/ Utilise cette méthode pour enchaîner maintenant. – mpen

0

Je pencherais vers une variante sur l'option 2. Dans jQuery $() est un appel de fonction. C# n'a pas de fonctions globales, un appel de fonction statique est le plus proche. J'utiliser une méthode qui vous indique créez une enveloppe comme ..

SharpQuery.Create("ul.first").Find(".foo") 

Je ne serais pas préoccupé par le raccourcissement SharpQuery à sq car IntelliSense signifie que les utilisateurs ne seront pas à taper la chose (et si ils ont des resharper ils ont seulement besoin de taper SQ de toute façon).

+0

Ils peuvent le nommer comme ils veulent. 'Sq' serait une instance de' SharpQuery'. 'SharpQuery.Create' ne fonctionnera pas comme une méthode statique parce que vous devez d'abord charger le document ... à moins que vous ne vouliez rendre le document statique, mais vous ne pouvez travailler qu'avec un seul document à la fois; une restriction arbitraire. – mpen