2009-09-27 8 views
3

J'ai un certain code C# qui marche des schémas XML en utilisant les classes Xml.Schema du framework .NET. Les diverses restrictions de types simples sont extraites dans le cadre sous la forme d'un ensemble de classes dérivées de Xml.Schema.XmlSchemaFacet. À moins qu'il y ait quelque chose que j'ai manqué, la seule façon de savoir lequel des types de facettes dérivés est une facette donnée est de la projeter spéculativement à l'un d'entre eux, en attrapant le InvalidCastOperation résultant en cas d'échec. Cela me laisse avec une fonction vraiment laide comme ceci:Comment est-ce que je devrais refactoriser une longue chaîne d'opérations de moulage spéculatives enveloppées d'essai

private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    try 
    { 
     handler.Length((XmlSchemaLengthFacet)facet); 
    } 
    catch(InvalidCastException) 
    { 
     try 
     { 
      handler.MinLength((XmlSchemaMinLengthFacet)facet); 
     } 
     catch(InvalidCastException) 
     { 
      try 
      { 
       handler.MaxLength((XmlSchemaMaxLengthFacet)facet); 
      } 
      catch(InvalidCastException) 
      { 
       ... 
      } 
     } 
    } 
} 

Je suppose qu'il doit y avoir des façons plus élégantes de le faire; soit en utilisant une propriété que j'ai manqué dans le framework .NET, soit avec un truc astucieux de truquage OO. Quelqu'un peut-il m'éclairer?

Répondre

7

Parce que je préfère des données de débogage au code de débogage, je le ferais comme ça, surtout si le code a dû gérer toutes les XmlSchemaFacet sous-classes:

Dictionary<Type, Action<XmlSchemaFacet>> HandlerMap = 
    new Dictionary<Type, Action<XmlSchemaFacet>> 
{ 
    {typeof(XmlSchemaLengthFacet), handler.Length}, 
    {typeof(XmlSchemaMinLengthFacet), handler.MinLength}, 
    {typeof(XmlSchemaMaxLengthFacet), handler.MaxLength} 
}; 

HandlerMap[facet.GetType()](facet); 

Ce jetterons un KeyNotFoundException si facet n » t d'un type connu. Notez que toutes les méthodes du gestionnaire devront convertir leur argument en XmlSchemaFacet, donc vous ne sauvegarderez probablement pas sur le total des lignes de code, mais vous économiserez définitivement sur le nombre de chemins dans votre code. Il arrive aussi un point où (en supposant que la carte est pré-compilée) les types de mappage aux méthodes avec un dictionnaire seront plus rapides que de parcourir une liste linéaire de types, ce qui est essentiellement ce que l'utilisation d'un tas de blocs .

+0

J'apprécie vraiment le look de ceci. Je l'essaie dans mon code maintenant, je reviendrai à upvote après qu'il soit parti. –

+1

Wow, merci encore pour ça, ça marche bien et le code est beaucoup plus propre maintenant. En outre, comme vous l'indiquez, le nouveau code s'exécute plus rapidement. Même si le nombre de tests unitaires dans le système a augmenté avec ce changement, le temps indiqué par NUnit a diminué de façon appréciable. Si je pouvais upvote plus d'une fois, je le ferais. :) –

+0

+1 * beaucoup * mieux que toutes les autres réponses ici. C'est ce que j'espérais dans mon commentaire sur la réponse de Bevan. – MusiGenesis

3

Vous pouvez essayer d'utiliser le mot-clé as - si la distribution échoue, vous obtenez un null au lieu d'une exception.

private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    var length = facet as XmlSchemaLengthFacet; 
    if (length != null) 
    { 
     handler.Length(length); 
     return; 
    } 

    var minlength = facet as XmlSchemaMinLengthFacet; 
    if (minlength != null) 
    { 
     handler.MinLength(minlength); 
     return; 
    } 

    var maxlength = facet as XmlSchemaMaxLengthFacet; 
    if (maxlength != null) 
    { 
     handler.MaxLength(maxlength); 
     return; 
    } 
    ... 
} 

Si vous aviez le contrôle sur les classes, je suggère d'utiliser une variante du motif de visiteurs (alias Double Despatch pour récupérer les informations de type plus proprement, mais puisque vous ne le faites pas, c'est une approche relativement simple .

Mise à jour: en utilisant la variable pour stocker le résultat de la fonte as évite la nécessité de passer par le

logique de vérification de type deux fois

Mise à jour 2. Lorsque C# 4 devient disponible , Vous serez en mesure d'utiliser dynamic pour faire le dispatching pour vous:

public class HandlerDemo 
{ 
    public void Handle(XmlSchemaLengthFacet facet) { ... } 
    public void Handle(XmlSchemaMinLengthFacet facet) { ... } 
    public void Handle(XmlSchemaMaxLengthFacet facet) { ... } 
    ... 
} 

private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    dynamic handler = new HandlerDemo(); 
    handler.Handle(facet); 
} 

Cela fonctionnera, car l'envoi de la méthode sur des objets dynamiques utilise les mêmes règles que le remplacement d'un mode normal, mais évalué à l'exécution au lieu de la compilation -temps. Sous le capot, le Dynamic Language Runtime (DLR) fera le même genre d'astuce que le code montré dans cette (et d'autres réponses), mais avec l'ajout de la mise en cache pour la performance.

+0

Est-ce que tu préfères vraiment ça à la mienne ou à la réponse de MusiGenesis? – ChaosPandion

+0

Personnellement, je n'aime * aucune * des réponses ici (y compris la mienne). Soit vous devez taper chaque type deux fois, ou vous devez taper le type et faire une vérification de null pour chaque type. Tout ce que vous faites dans cette méthode est d'associer chaque type à une méthode particulière dans 'handler' (quel qu'il soit) - il doit y avoir une méthode LINQ-y plus propre. – MusiGenesis

6

Vous pouvez essayer d'utiliser le mot-clé as. D'autres personnes ont recommandé d'utiliser le mot clé is à la place. J'ai trouvé this pour être une excellente explication de pourquoi as est mieux.

Quelques exemples de code:

private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    XmlSchemaLengthFacet lengthFacet = facet as XmlSchemaLengthFacet; 
    if (lengthFacet != null) 
    { 
    handler.Length(lengthFacet); 
    } 
    else 
    { 
    // Re-try with XmlSchemaMinLengthFacet, etc. 
    } 
} 
+0

Merci, j'ai remis cette réponse à plus tard car c'était certainement une amélioration par rapport à mon désordre initial, mais la réponse de Robert était spectaculairement bonne du point de vue de la propreté du code, donc il est accepté. –

+0

@Matt: voir ma mise à jour sur le benchmarking 'is' vs' as'. Je mettrais cela dans la catégorie "vraiment pas d'importance". – MusiGenesis

4
private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    if (facet is XmlSchemaLengthFacet) 
    { 
     handler.Length((XmlSchemaLengthFacet)facet); 
    } 
    else if (facet is XmlSchemaMinLengthFacet) 
    { 
     handler.MinLength((XmlSchemaMinLengthFacet)facet); 
    } 
    else if (facet is XmlSchemaMaxLengthFacet) 
    { 
     handler.MinLength((XmlSchemaMaxLengthFacet)facet); 
    } 

    // etc. 
} 

Mise à jour: j'ai décidé de comparer les différentes méthodes décrites ici (is vs as). Voici le code que je:

object c1 = new Class1(); 
int trials = 10000000; 
Class1 tester; 
Stopwatch watch = Stopwatch.StartNew(); 
for (int i = 0; i < trials; i++) 
{ 
    if (c1 is Class1) 
    { 
     tester = (Class1)c1; 
    } 
} 
watch.Stop(); 
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~104 ms 
watch.Reset(); 
watch.Start(); 
for (int i = 0; i < trials; i++) 
{ 
    tester = c1 as Class1; 
    if (tester != null) 
    { 
     // 
    } 
} 
watch.Stop(); 
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~86 ms 
watch.Reset(); 
watch.Start(); 
for (int i = 0; i < trials; i++) 
{ 
    if (c1 is Class1) 
    { 
     // 
    } 
} 
watch.Stop();  
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~74 ms 
watch.Reset(); 
watch.Start(); 
for (int i = 0; i < trials; i++) 
{ 
    // 
} 
watch.Stop();  
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~50 ms 

Comme prévu, en utilisant le mot-clé as puis la vérification de null est plus rapide que d'utiliser le mot-clé is et coulée (36 ms contre 54 ms, soustrayant le coût des la boucle elle-même).

Cependant, en utilisant le mot-clé is puis pas coulée est encore plus rapide (24ms), ce qui signifie que trouver le bon type avec une série de is contrôles et seulement coulée une fois lorsque le type est identifié peut être plus rapide (en fonction du nombre de vérifications de type différentes qui doivent être effectuées dans cette méthode avant que le type correct ne soit identifié). Le point le plus profond, cependant, est que le nombre d'essais dans ce test est de 10 millions,, ce qui signifie que la méthode que vous utilisez ne fait pas vraiment de différence. En utilisant is et le moulage prend 0.0000054 millisecondes, tout en utilisant as et en vérifiant la null prend 0.0000036 millisecondes (sur mon ancien cahier).

+0

Salut, merci d'avoir répondu, je l'ai upvoted ceci, mais Robert obtient la réponse acceptée pour sa solution ci-dessous. –

+0

@Phil: pas de problème, j'aime mieux sa réponse, aussi. – MusiGenesis

1

Ceci est une bonne façon de le faire sans aucun essai.

if (facet is XmlSchemaLengthFacet) 
{ 
    handler.Length((XmlSchemaLengthFacet)facet); 
} 
else if (facet is XmlSchemaMinLengthFacet) 
{ 
    handler.MinLength((XmlSchemaMinLengthFacet)facet); 
} 
else if (facet is XmlSchemaMaxLengthFacet) 
{ 
    handler.MaxLength((XmlSchemaMaxLengthFacet)facet); 
} 
else 
{ 
    //Handle Error 
} 
0
  • utilisation « est » pour déterminer si un objet est d'un type donné

  • utilisation « comme » pour la conversion de type, il est plus rapide que la coulée normale et ne lancer des exceptions, il reuturns nulle en cas d'erreur au lieu

Vous pouvez le faire comme ceci:

private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    if (facet is XmlSchemaLengthFacet) 
    { 
     handler.Length(facet as XmlSchemaLengthFacet); 
    } 
    else if (facet is XmlSchemaMinLengthFacet) 
    { 
     handler.MinLength(facet as XmlSchemaMinLengthFacet); 
    } 
    else if (facet is XmlSchemaMaxLengthFacet) 
    { 
     handler.MaxLength(facet as XmlSchemaMaxLengthFacet); 
    } 
} 
+0

Je préfère utiliser "as" et vérifier! = Null - de cette façon, l'objet en question ne doit être inspecté (par réflexion) qu'une seule fois, et le code est toujours totalement sûr. –

+0

L'utilisation de "is" et "as" est redondante.J'utiliserais plutôt la solution de Bevan (plus efficace) ou la solution de MusiGenesis (plus simple). – TrueWill

Questions connexes